From 5a9e67c93e562a8e7c1af0a3dee0a00fb76382bf Mon Sep 17 00:00:00 2001 From: Dean Gardiner Date: Sun, 17 Jan 2016 00:09:47 +1300 Subject: [PATCH] Updated rtorrent-python library - Implemented "requests" transport - Support for digest authentication (via the "requests" transport) #4854 --- libs/rtorrent/__init__.py | 123 ++---------- libs/rtorrent/connection.py | 158 ++++++++++++++++ libs/rtorrent/lib/xmlrpc/basic_auth.py | 95 ---------- libs/rtorrent/lib/xmlrpc/clients/__init__.py | 0 libs/rtorrent/lib/xmlrpc/clients/http.py | 23 +++ libs/rtorrent/lib/xmlrpc/clients/scgi.py | 152 +++++++++++++++ libs/rtorrent/lib/xmlrpc/http.py | 23 --- libs/rtorrent/lib/xmlrpc/scgi.py | 219 ---------------------- libs/rtorrent/lib/xmlrpc/transports/__init__.py | 0 libs/rtorrent/lib/xmlrpc/transports/basic_auth.py | 95 ++++++++++ libs/rtorrent/lib/xmlrpc/transports/requests_.py | 91 +++++++++ libs/rtorrent/lib/xmlrpc/transports/scgi.py | 155 +++++++++++++++ libs/rtorrent/rpc/__init__.py | 6 +- 13 files changed, 692 insertions(+), 448 deletions(-) create mode 100644 libs/rtorrent/connection.py delete mode 100644 libs/rtorrent/lib/xmlrpc/basic_auth.py create mode 100644 libs/rtorrent/lib/xmlrpc/clients/__init__.py create mode 100644 libs/rtorrent/lib/xmlrpc/clients/http.py create mode 100644 libs/rtorrent/lib/xmlrpc/clients/scgi.py delete mode 100755 libs/rtorrent/lib/xmlrpc/http.py delete mode 100644 libs/rtorrent/lib/xmlrpc/scgi.py create mode 100644 libs/rtorrent/lib/xmlrpc/transports/__init__.py create mode 100644 libs/rtorrent/lib/xmlrpc/transports/basic_auth.py create mode 100644 libs/rtorrent/lib/xmlrpc/transports/requests_.py create mode 100644 libs/rtorrent/lib/xmlrpc/transports/scgi.py diff --git a/libs/rtorrent/__init__.py b/libs/rtorrent/__init__.py index a3f1607..a23640b 100755 --- a/libs/rtorrent/__init__.py +++ b/libs/rtorrent/__init__.py @@ -22,13 +22,11 @@ import os.path import time import xmlrpclib +from rtorrent.connection import Connection from rtorrent.common import find_torrent, join_uri, \ update_uri, is_valid_port, convert_version_tuple_to_str from rtorrent.lib.torrentparser import TorrentParser -from rtorrent.lib.xmlrpc.http import HTTPServerProxy -from rtorrent.lib.xmlrpc.scgi import SCGIServerProxy from rtorrent.rpc import Method -from rtorrent.lib.xmlrpc.basic_auth import BasicAuthTransport from rtorrent.torrent import Torrent from rtorrent.group import Group import rtorrent.rpc # @UnresolvedImport @@ -38,119 +36,28 @@ __author__ = "Chris Lucas" __contact__ = "chris@chrisjlucas.com" __license__ = "MIT" -MIN_RTORRENT_VERSION = (0, 8, 1) -MIN_RTORRENT_VERSION_STR = convert_version_tuple_to_str(MIN_RTORRENT_VERSION) - class RTorrent: """ Create a new rTorrent connection """ rpc_prefix = None - def __init__(self, uri, username=None, password=None, - verify=False, sp=None, sp_kwargs=None): - self.uri = self._transform_uri(uri) # : From X{__init__(self, url)} - - self.username = username - self.password = password - - self.scheme = urllib.splittype(self.uri)[0] - - if sp: - self.sp = sp - elif self.scheme in ['http', 'https']: - self.sp = HTTPServerProxy - elif self.scheme == 'scgi': - self.sp = SCGIServerProxy - else: - raise NotImplementedError() - - self.sp_kwargs = sp_kwargs or {} + def __init__(self, uri, auth=None, verify_server=False, verify_ssl=True, sp=None, sp_kwargs=None): + self.connection = Connection(uri, auth, verify_ssl, sp, sp_kwargs) self.torrents = [] # : List of L{Torrent} instances - self._rpc_methods = [] # : List of rTorrent RPC methods - self._torrent_cache = [] - self._client_version_tuple = () - - if verify is True: - self._verify_conn() - def _transform_uri(self, uri): - scheme = urllib.splittype(uri)[0] - - if scheme == 'httprpc' or scheme.startswith('httprpc+'): - # Try find HTTPRPC transport (token after '+' in 'httprpc+https'), otherwise assume HTTP - transport = scheme[scheme.index('+') + 1:] if '+' in scheme else 'http' + self._torrent_cache = [] - # Transform URI with new path and scheme - uri = join_uri(uri, 'plugins/httprpc/action.php', construct=False) - return update_uri(uri, scheme=transport) + # Verify connection is valid + if verify_server is True: + self.connection.verify() - return uri + @property + def client(self): + return self.connection.client def _get_conn(self): - """Get ServerProxy instance""" - - if self.username and self.password: - if self.scheme == 'scgi': - raise NotImplementedError() - - secure = self.scheme == 'https' - - return self.sp( - self.uri, - transport=BasicAuthTransport(secure, self.username, self.password), - **self.sp_kwargs - ) - - return self.sp(self.uri, **self.sp_kwargs) - - def _verify_conn(self): - # check for rpc methods that should be available - assert "system.client_version" in self._get_rpc_methods(), "Required RPC method not available." - assert "system.library_version" in self._get_rpc_methods(), "Required RPC method not available." - - # minimum rTorrent version check - assert self._meets_version_requirement() is True,\ - "Error: Minimum rTorrent version required is {0}".format( - MIN_RTORRENT_VERSION_STR) - - def test_connection(self): - try: - self._verify_conn() - except: - return False - return True - - def _meets_version_requirement(self): - return self._get_client_version_tuple() >= MIN_RTORRENT_VERSION - - def _get_client_version_tuple(self): - conn = self._get_conn() - - if not self._client_version_tuple: - if not hasattr(self, "client_version"): - setattr(self, "client_version", - conn.system.client_version()) - - rtver = getattr(self, "client_version") - self._client_version_tuple = tuple([int(i) for i in - rtver.split(".")]) - - return self._client_version_tuple - - def _update_rpc_methods(self): - self._rpc_methods = self._get_conn().system.listMethods() - - return self._rpc_methods - - def _get_rpc_methods(self): - """ Get list of raw RPC commands - - @return: raw RPC commands - @rtype: list - """ - - return(self._rpc_methods or self._update_rpc_methods()) + return self.client def get_torrents(self, view="main"): """Get list of all torrents in specified view @@ -341,7 +248,7 @@ class RTorrent: assert view is not None, "view parameter required on non-persistent groups" p.group.insert('', name, view) - self._update_rpc_methods() + self.connection._update_rpc_methods() def get_group(self, name): assert name is not None, "group name required" @@ -424,8 +331,8 @@ def _build_class_methods(class_obj): def __compare_rpc_methods(rt_new, rt_old): from pprint import pprint - rt_new_methods = set(rt_new._get_rpc_methods()) - rt_old_methods = set(rt_old._get_rpc_methods()) + rt_new_methods = set(rt_new.connection._get_rpc_methods()) + rt_old_methods = set(rt_old.connection._get_rpc_methods()) print("New Methods:") pprint(rt_new_methods - rt_old_methods) print("Methods not in new rTorrent:") @@ -440,7 +347,7 @@ def __check_supported_methods(rt): rtorrent.torrent.methods + rtorrent.tracker.methods + rtorrent.peer.methods]) - all_methods = set(rt._get_rpc_methods()) + all_methods = set(rt.connection._get_rpc_methods()) print("Methods NOT in supported methods") pprint(all_methods - supported_methods) diff --git a/libs/rtorrent/connection.py b/libs/rtorrent/connection.py new file mode 100644 index 0000000..a79381c --- /dev/null +++ b/libs/rtorrent/connection.py @@ -0,0 +1,158 @@ +import logging +import urllib + +from rtorrent.common import convert_version_tuple_to_str, join_uri, update_uri +from rtorrent.lib.xmlrpc.clients.http import HTTPServerProxy +from rtorrent.lib.xmlrpc.clients.scgi import SCGIServerProxy +from rtorrent.lib.xmlrpc.transports.basic_auth import BasicAuthTransport + +# Try import requests transport (optional) +try: + from rtorrent.lib.xmlrpc.transports.requests_ import RequestsTransport +except ImportError: + RequestsTransport = None + +MIN_RTORRENT_VERSION = (0, 8, 1) +MIN_RTORRENT_VERSION_STR = convert_version_tuple_to_str(MIN_RTORRENT_VERSION) + +log = logging.getLogger(__name__) + + +class Connection(object): + def __init__(self, uri, auth=None, verify_ssl=True, sp=None, sp_kwargs=None): + self.auth = auth + self.verify_ssl = verify_ssl + + # Transform + Parse URI + self.uri = self._transform_uri(uri) + self.scheme = urllib.splittype(self.uri)[0] + + # Construct RPC Client + self.sp = self._get_sp(self.scheme, sp) + self.sp_kwargs = sp_kwargs or {} + + self._client = None + self._client_version_tuple = () + self._rpc_methods = [] + + @property + def client(self): + if self._client is None: + # Construct new client + self._client = self.connect() + + # Return client + return self._client + + def connect(self): + log.debug('Connecting to server: %r', self.uri) + + if self.auth: + # Construct server proxy with authentication transport + return self.sp(self.uri, transport=self._construct_transport(), **self.sp_kwargs) + + # Construct plain server proxy + return self.sp(self.uri, **self.sp_kwargs) + + def test(self): + try: + self.verify() + except: + return False + + return True + + def verify(self): + # check for rpc methods that should be available + assert "system.client_version" in self._get_rpc_methods(), "Required RPC method not available." + assert "system.library_version" in self._get_rpc_methods(), "Required RPC method not available." + + # minimum rTorrent version check + assert self._meets_version_requirement() is True,\ + "Error: Minimum rTorrent version required is {0}".format(MIN_RTORRENT_VERSION_STR) + + # + # Private methods + # + + def _construct_transport(self): + # Ensure "auth" parameter is valid + if type(self.auth) is not tuple or len(self.auth) != 3: + raise ValueError('Invalid "auth" parameter format') + + # Construct transport with authentication details + method, _, _ = self.auth + secure = self.scheme == 'https' + + log.debug('Constructing transport for scheme: %r, authentication method: %r', self.scheme, method) + + # Use requests transport (if available) + if RequestsTransport and method in ['basic', 'digest']: + return RequestsTransport( + secure, self.auth, + verify_ssl=self.verify_ssl + ) + + # Use basic authentication transport + if method == 'basic': + return BasicAuthTransport(secure, self.auth) + + # Unsupported authentication method + if method == 'digest': + raise Exception('Digest authentication requires the "requests" library') + + raise NotImplementedError('Unknown authentication method: %r' % method) + + def _get_client_version_tuple(self): + if not self._client_version_tuple: + if not hasattr(self, "client_version"): + setattr(self, "client_version", self.client.system.client_version()) + + rtver = getattr(self, "client_version") + self._client_version_tuple = tuple([int(i) for i in rtver.split(".")]) + + return self._client_version_tuple + + def _get_rpc_methods(self): + """ Get list of raw RPC commands + + @return: raw RPC commands + @rtype: list + """ + + return(self._rpc_methods or self._update_rpc_methods()) + + @staticmethod + def _get_sp(scheme, sp): + if sp: + return sp + + if scheme in ['http', 'https']: + return HTTPServerProxy + + if scheme == 'scgi': + return SCGIServerProxy + + raise NotImplementedError() + + def _meets_version_requirement(self): + return self._get_client_version_tuple() >= MIN_RTORRENT_VERSION + + @staticmethod + def _transform_uri(uri): + scheme = urllib.splittype(uri)[0] + + if scheme == 'httprpc' or scheme.startswith('httprpc+'): + # Try find HTTPRPC transport (token after '+' in 'httprpc+https'), otherwise assume HTTP + transport = scheme[scheme.index('+') + 1:] if '+' in scheme else 'http' + + # Transform URI with new path and scheme + uri = join_uri(uri, 'plugins/httprpc/action.php', construct=False) + return update_uri(uri, scheme=transport) + + return uri + + def _update_rpc_methods(self): + self._rpc_methods = self.client.system.listMethods() + + return self._rpc_methods diff --git a/libs/rtorrent/lib/xmlrpc/basic_auth.py b/libs/rtorrent/lib/xmlrpc/basic_auth.py deleted file mode 100644 index c5654a2..0000000 --- a/libs/rtorrent/lib/xmlrpc/basic_auth.py +++ /dev/null @@ -1,95 +0,0 @@ -# -# Copyright (c) 2013 Dean Gardiner, -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -from base64 import b64encode -import httplib -import xmlrpclib - - -class BasicAuthTransport(xmlrpclib.Transport): - def __init__(self, secure=False, username=None, password=None): - xmlrpclib.Transport.__init__(self) - - self.secure = secure - - self.username = username - self.password = password - - def send_auth(self, h): - if not self.username or not self.password: - return - - auth = b64encode("%s:%s" % (self.username, self.password)) - - h.putheader('Authorization', "Basic %s" % auth) - - def make_connection(self, host): - if self._connection and host == self._connection[0]: - return self._connection[1] - - chost, self._extra_headers, x509 = self.get_host_info(host) - - if self.secure: - try: - self._connection = host, httplib.HTTPSConnection(chost, None, **(x509 or {})) - except AttributeError: - raise NotImplementedError( - "your version of httplib doesn't support HTTPS" - ) - else: - self._connection = host, httplib.HTTPConnection(chost) - - return self._connection[1] - - - def single_request(self, host, handler, request_body, verbose=0): - # issue XML-RPC request - - h = self.make_connection(host) - if verbose: - h.set_debuglevel(1) - - try: - self.send_request(h, handler, request_body) - self.send_host(h, host) - self.send_user_agent(h) - self.send_auth(h) - self.send_content(h, request_body) - - response = h.getresponse(buffering=True) - if response.status == 200: - self.verbose = verbose - return self.parse_response(response) - except xmlrpclib.Fault: - raise - except Exception: - self.close() - raise - - #discard any response data and raise exception - if response.getheader("content-length", 0): - response.read() - raise xmlrpclib.ProtocolError( - host + handler, - response.status, response.reason, - response.msg, - ) diff --git a/libs/rtorrent/lib/xmlrpc/clients/__init__.py b/libs/rtorrent/lib/xmlrpc/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/rtorrent/lib/xmlrpc/clients/http.py b/libs/rtorrent/lib/xmlrpc/clients/http.py new file mode 100644 index 0000000..3eb8521 --- /dev/null +++ b/libs/rtorrent/lib/xmlrpc/clients/http.py @@ -0,0 +1,23 @@ +# Copyright (c) 2013 Chris Lucas, +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from rtorrent.compat import xmlrpclib + +HTTPServerProxy = xmlrpclib.ServerProxy diff --git a/libs/rtorrent/lib/xmlrpc/clients/scgi.py b/libs/rtorrent/lib/xmlrpc/clients/scgi.py new file mode 100644 index 0000000..ade57c5 --- /dev/null +++ b/libs/rtorrent/lib/xmlrpc/clients/scgi.py @@ -0,0 +1,152 @@ +#!/usr/bin/python + +# rtorrent_xmlrpc +# (c) 2011 Roger Que +# +# Modified portions: +# (c) 2013 Dean Gardiner +# +# Python module for interacting with rtorrent's XML-RPC interface +# directly over SCGI, instead of through an HTTP server intermediary. +# Inspired by Glenn Washburn's xmlrpc2scgi.py [1], but subclasses the +# built-in xmlrpclib classes so that it is compatible with features +# such as MultiCall objects. +# +# [1] +# +# Usage: server = SCGIServerProxy('scgi://localhost:7000/') +# server = SCGIServerProxy('scgi:///path/to/scgi.sock') +# print server.system.listMethods() +# mc = xmlrpclib.MultiCall(server) +# mc.get_up_rate() +# mc.get_down_rate() +# print mc() +# +# +# +# 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. +# +# +# +# Portions based on Python's xmlrpclib: +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. + +import urllib +import xmlrpclib + +from rtorrent.lib.xmlrpc.transports.scgi import SCGITransport + + +class SCGIServerProxy(xmlrpclib.ServerProxy): + def __init__(self, uri, transport=None, encoding=None, verbose=False, + allow_none=False, use_datetime=False): + type, uri = urllib.splittype(uri) + if type not in ('scgi'): + raise IOError('unsupported XML-RPC protocol') + self.__host, self.__handler = urllib.splithost(uri) + if not self.__handler: + self.__handler = '/' + + if transport is None: + transport = SCGITransport(use_datetime=use_datetime) + self.__transport = transport + + self.__encoding = encoding + self.__verbose = verbose + self.__allow_none = allow_none + + def __close(self): + self.__transport.close() + + def __request(self, methodname, params): + # call a method on the remote server + + request = xmlrpclib.dumps(params, methodname, encoding=self.__encoding, + allow_none=self.__allow_none) + + response = self.__transport.request( + self.__host, + self.__handler, + request, + verbose=self.__verbose + ) + + if len(response) == 1: + response = response[0] + + return response + + def __repr__(self): + return ( + "" % + (self.__host, self.__handler) + ) + + __str__ = __repr__ + + def __getattr__(self, name): + # magic method dispatcher + return xmlrpclib._Method(self.__request, name) + + # note: to call a remote object with an non-standard name, use + # result getattr(server, "strange-python-name")(args) + + def __call__(self, attr): + """A workaround to get special attributes on the ServerProxy + without interfering with the magic __getattr__ + """ + if attr == "close": + return self.__close + elif attr == "transport": + return self.__transport + raise AttributeError("Attribute %r not found" % (attr,)) diff --git a/libs/rtorrent/lib/xmlrpc/http.py b/libs/rtorrent/lib/xmlrpc/http.py deleted file mode 100755 index 3eb8521..0000000 --- a/libs/rtorrent/lib/xmlrpc/http.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2013 Chris Lucas, -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -from rtorrent.compat import xmlrpclib - -HTTPServerProxy = xmlrpclib.ServerProxy diff --git a/libs/rtorrent/lib/xmlrpc/scgi.py b/libs/rtorrent/lib/xmlrpc/scgi.py deleted file mode 100644 index 5ba61fa..0000000 --- a/libs/rtorrent/lib/xmlrpc/scgi.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/python - -# rtorrent_xmlrpc -# (c) 2011 Roger Que -# -# Modified portions: -# (c) 2013 Dean Gardiner -# -# Python module for interacting with rtorrent's XML-RPC interface -# directly over SCGI, instead of through an HTTP server intermediary. -# Inspired by Glenn Washburn's xmlrpc2scgi.py [1], but subclasses the -# built-in xmlrpclib classes so that it is compatible with features -# such as MultiCall objects. -# -# [1] -# -# Usage: server = SCGIServerProxy('scgi://localhost:7000/') -# server = SCGIServerProxy('scgi:///path/to/scgi.sock') -# print server.system.listMethods() -# mc = xmlrpclib.MultiCall(server) -# mc.get_up_rate() -# mc.get_down_rate() -# print mc() -# -# -# -# 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. -# -# -# -# Portions based on Python's xmlrpclib: -# -# Copyright (c) 1999-2002 by Secret Labs AB -# Copyright (c) 1999-2002 by Fredrik Lundh -# -# By obtaining, using, and/or copying this software and/or its -# associated documentation, you agree that you have read, understood, -# and will comply with the following terms and conditions: -# -# Permission to use, copy, modify, and distribute this software and -# its associated documentation for any purpose and without fee is -# hereby granted, provided that the above copyright notice appears in -# all copies, and that both that copyright notice and this permission -# notice appear in supporting documentation, and that the name of -# Secret Labs AB or the author not be used in advertising or publicity -# pertaining to distribution of the software without specific, written -# prior permission. -# -# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD -# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- -# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR -# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY -# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS -# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -# OF THIS SOFTWARE. - -import httplib -import re -import socket -import urllib -import xmlrpclib -import errno - - -class SCGITransport(xmlrpclib.Transport): - # Added request() from Python 2.7 xmlrpclib here to backport to Python 2.6 - def request(self, host, handler, request_body, verbose=0): - #retry request once if cached connection has gone cold - for i in (0, 1): - try: - return self.single_request(host, handler, request_body, verbose) - except socket.error, e: - if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): - raise - except httplib.BadStatusLine: #close after we sent request - if i: - raise - - def single_request(self, host, handler, request_body, verbose=0): - # Add SCGI headers to the request. - headers = {'CONTENT_LENGTH': str(len(request_body)), 'SCGI': '1'} - header = '\x00'.join(('%s\x00%s' % item for item in headers.iteritems())) + '\x00' - header = '%d:%s' % (len(header), header) - request_body = '%s,%s' % (header, request_body) - - sock = None - - try: - if host: - host, port = urllib.splitport(host) - addrinfo = socket.getaddrinfo(host, int(port), socket.AF_INET, - socket.SOCK_STREAM) - sock = socket.socket(*addrinfo[0][:3]) - sock.connect(addrinfo[0][4]) - else: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(handler) - - self.verbose = verbose - - sock.send(request_body) - return self.parse_response(sock.makefile()) - finally: - if sock: - sock.close() - - def parse_response(self, response): - p, u = self.getparser() - - response_body = '' - while True: - data = response.read(1024) - if not data: - break - response_body += data - - # Remove SCGI headers from the response. - response_header, response_body = re.split(r'\n\s*?\n', response_body, - maxsplit=1) - - if self.verbose: - print 'body:', repr(response_body) - - p.feed(response_body) - p.close() - - return u.close() - - -class SCGIServerProxy(xmlrpclib.ServerProxy): - def __init__(self, uri, transport=None, encoding=None, verbose=False, - allow_none=False, use_datetime=False): - type, uri = urllib.splittype(uri) - if type not in ('scgi'): - raise IOError('unsupported XML-RPC protocol') - self.__host, self.__handler = urllib.splithost(uri) - if not self.__handler: - self.__handler = '/' - - if transport is None: - transport = SCGITransport(use_datetime=use_datetime) - self.__transport = transport - - self.__encoding = encoding - self.__verbose = verbose - self.__allow_none = allow_none - - def __close(self): - self.__transport.close() - - def __request(self, methodname, params): - # call a method on the remote server - - request = xmlrpclib.dumps(params, methodname, encoding=self.__encoding, - allow_none=self.__allow_none) - - response = self.__transport.request( - self.__host, - self.__handler, - request, - verbose=self.__verbose - ) - - if len(response) == 1: - response = response[0] - - return response - - def __repr__(self): - return ( - "" % - (self.__host, self.__handler) - ) - - __str__ = __repr__ - - def __getattr__(self, name): - # magic method dispatcher - return xmlrpclib._Method(self.__request, name) - - # note: to call a remote object with an non-standard name, use - # result getattr(server, "strange-python-name")(args) - - def __call__(self, attr): - """A workaround to get special attributes on the ServerProxy - without interfering with the magic __getattr__ - """ - if attr == "close": - return self.__close - elif attr == "transport": - return self.__transport - raise AttributeError("Attribute %r not found" % (attr,)) diff --git a/libs/rtorrent/lib/xmlrpc/transports/__init__.py b/libs/rtorrent/lib/xmlrpc/transports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/rtorrent/lib/xmlrpc/transports/basic_auth.py b/libs/rtorrent/lib/xmlrpc/transports/basic_auth.py new file mode 100644 index 0000000..c5654a2 --- /dev/null +++ b/libs/rtorrent/lib/xmlrpc/transports/basic_auth.py @@ -0,0 +1,95 @@ +# +# Copyright (c) 2013 Dean Gardiner, +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from base64 import b64encode +import httplib +import xmlrpclib + + +class BasicAuthTransport(xmlrpclib.Transport): + def __init__(self, secure=False, username=None, password=None): + xmlrpclib.Transport.__init__(self) + + self.secure = secure + + self.username = username + self.password = password + + def send_auth(self, h): + if not self.username or not self.password: + return + + auth = b64encode("%s:%s" % (self.username, self.password)) + + h.putheader('Authorization', "Basic %s" % auth) + + def make_connection(self, host): + if self._connection and host == self._connection[0]: + return self._connection[1] + + chost, self._extra_headers, x509 = self.get_host_info(host) + + if self.secure: + try: + self._connection = host, httplib.HTTPSConnection(chost, None, **(x509 or {})) + except AttributeError: + raise NotImplementedError( + "your version of httplib doesn't support HTTPS" + ) + else: + self._connection = host, httplib.HTTPConnection(chost) + + return self._connection[1] + + + def single_request(self, host, handler, request_body, verbose=0): + # issue XML-RPC request + + h = self.make_connection(host) + if verbose: + h.set_debuglevel(1) + + try: + self.send_request(h, handler, request_body) + self.send_host(h, host) + self.send_user_agent(h) + self.send_auth(h) + self.send_content(h, request_body) + + response = h.getresponse(buffering=True) + if response.status == 200: + self.verbose = verbose + return self.parse_response(response) + except xmlrpclib.Fault: + raise + except Exception: + self.close() + raise + + #discard any response data and raise exception + if response.getheader("content-length", 0): + response.read() + raise xmlrpclib.ProtocolError( + host + handler, + response.status, response.reason, + response.msg, + ) diff --git a/libs/rtorrent/lib/xmlrpc/transports/requests_.py b/libs/rtorrent/lib/xmlrpc/transports/requests_.py new file mode 100644 index 0000000..779b744 --- /dev/null +++ b/libs/rtorrent/lib/xmlrpc/transports/requests_.py @@ -0,0 +1,91 @@ +import requests +import requests.auth +import xmlrpclib + + +class RequestsTransport(xmlrpclib.Transport): + def __init__(self, secure, auth=None, proxies=None, verify_ssl=True): + xmlrpclib.Transport.__init__(self) + + self.secure = secure + + # Construct session + self.session = requests.Session() + self.session.auth = self.parse_auth(auth) + self.session.proxies = proxies or {} + self.session.verify = verify_ssl + + @property + def scheme(self): + if self.secure: + return 'https' + + return 'http' + + def build_url(self, host, handler): + return '%s://%s' % (self.scheme, host + handler) + + def request(self, host, handler, request_body, verbose=0): + # Retry request once if cached connection has gone cold + for i in (0, 1): + try: + return self.single_request(host, handler, request_body, verbose) + except requests.ConnectionError: + if i: + raise + except requests.Timeout: + if i: + raise + + def single_request(self, host, handler, request_body, verbose=0): + url = self.build_url(host, handler) + + # Send request + response = self.session.post( + url, + data=request_body, + headers={ + 'Content-Type': 'text/xml' + }, + stream=True + ) + + if response.status_code == 200: + return self.parse_response(response) + + # Invalid response returned + raise xmlrpclib.ProtocolError( + host + handler, + response.status_code, response.reason, + response.headers + ) + + def parse_auth(self, auth): + # Parse "auth" parameter + if type(auth) is not tuple or len(auth) != 3: + return None + + method, username, password = auth + + # Basic Authentication + if method == 'basic': + return requests.auth.HTTPBasicAuth(username, password) + + # Digest Authentication + if method == 'digest': + return requests.auth.HTTPDigestAuth(username, password) + + raise NotImplementedError('Unsupported authentication method: %r' % method) + + def parse_response(self, response): + p, u = self.getparser() + + # Write chunks to parser + for chunk in response.iter_content(1024): + p.feed(chunk) + + # Close parser + p.close() + + # Close unmarshaller + return u.close() diff --git a/libs/rtorrent/lib/xmlrpc/transports/scgi.py b/libs/rtorrent/lib/xmlrpc/transports/scgi.py new file mode 100644 index 0000000..e63b0db --- /dev/null +++ b/libs/rtorrent/lib/xmlrpc/transports/scgi.py @@ -0,0 +1,155 @@ +#!/usr/bin/python + +# rtorrent_xmlrpc +# (c) 2011 Roger Que +# +# Modified portions: +# (c) 2013 Dean Gardiner +# +# Python module for interacting with rtorrent's XML-RPC interface +# directly over SCGI, instead of through an HTTP server intermediary. +# Inspired by Glenn Washburn's xmlrpc2scgi.py [1], but subclasses the +# built-in xmlrpclib classes so that it is compatible with features +# such as MultiCall objects. +# +# [1] +# +# Usage: server = SCGIServerProxy('scgi://localhost:7000/') +# server = SCGIServerProxy('scgi:///path/to/scgi.sock') +# print server.system.listMethods() +# mc = xmlrpclib.MultiCall(server) +# mc.get_up_rate() +# mc.get_down_rate() +# print mc() +# +# +# +# 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. +# +# +# +# Portions based on Python's xmlrpclib: +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. + + +import errno +import httplib +import re +import socket +import urllib +import xmlrpclib + + +class SCGITransport(xmlrpclib.Transport): + # Added request() from Python 2.7 xmlrpclib here to backport to Python 2.6 + def request(self, host, handler, request_body, verbose=0): + #retry request once if cached connection has gone cold + for i in (0, 1): + try: + return self.single_request(host, handler, request_body, verbose) + except socket.error, e: + if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): + raise + except httplib.BadStatusLine: #close after we sent request + if i: + raise + + def single_request(self, host, handler, request_body, verbose=0): + # Add SCGI headers to the request. + headers = {'CONTENT_LENGTH': str(len(request_body)), 'SCGI': '1'} + header = '\x00'.join(('%s\x00%s' % item for item in headers.iteritems())) + '\x00' + header = '%d:%s' % (len(header), header) + request_body = '%s,%s' % (header, request_body) + + sock = None + + try: + if host: + host, port = urllib.splitport(host) + addrinfo = socket.getaddrinfo(host, int(port), socket.AF_INET, + socket.SOCK_STREAM) + sock = socket.socket(*addrinfo[0][:3]) + sock.connect(addrinfo[0][4]) + else: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(handler) + + self.verbose = verbose + + sock.send(request_body) + return self.parse_response(sock.makefile()) + finally: + if sock: + sock.close() + + def parse_response(self, response): + p, u = self.getparser() + + response_body = '' + while True: + data = response.read(1024) + if not data: + break + response_body += data + + # Remove SCGI headers from the response. + response_header, response_body = re.split(r'\n\s*?\n', response_body, + maxsplit=1) + + if self.verbose: + print 'body:', repr(response_body) + + p.feed(response_body) + p.close() + + return u.close() diff --git a/libs/rtorrent/rpc/__init__.py b/libs/rtorrent/rpc/__init__.py index 116ca1c..607d409 100755 --- a/libs/rtorrent/rpc/__init__.py +++ b/libs/rtorrent/rpc/__init__.py @@ -45,7 +45,7 @@ def get_varname(rpc_call): def _handle_unavailable_rpc_method(method, rt_obj): msg = "Method isn't available." - if rt_obj._get_client_version_tuple() < method.min_version: + if rt_obj.connection._get_client_version_tuple() < method.min_version: msg = "This method is only available in " \ "RTorrent version v{0} or later".format( convert_version_tuple_to_str(method.min_version)) @@ -108,8 +108,8 @@ class Method: return(False) def is_available(self, rt_obj): - if rt_obj._get_client_version_tuple() < self.min_version or \ - self.rpc_call not in rt_obj._get_rpc_methods(): + if rt_obj.connection._get_client_version_tuple() < self.min_version or \ + self.rpc_call not in rt_obj.connection._get_rpc_methods(): return(False) else: return(True)