From 09c0aa993fc533542c666b0c4ee959484c42c7e4 Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Fri, 5 Jul 2013 19:08:39 +0200 Subject: [PATCH] Implement JSON RPC myself. Write my own implementation of JSON RPC and HTTP queries, because the jsonrpc module failed to let me configure what to do with failed HTTP authentications. --- src/bitmessageqt/__init__.py | 2 +- src/class_namecoin.py | 142 +++++++++++++++++++++++++++++------ 2 files changed, 119 insertions(+), 25 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index d6f6d369..6374a81f 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -14,7 +14,7 @@ except ImportError: from addresses import * import shared from bitmessageui import * -from class_namecoin import * +from class_namecoin import namecoinConnection from newaddressdialog import * from newsubscriptiondialog import * from regenerateaddresses import * diff --git a/src/class_namecoin.py b/src/class_namecoin.py index d5a9543a..f11cd97b 100644 --- a/src/class_namecoin.py +++ b/src/class_namecoin.py @@ -1,46 +1,140 @@ +# Copyright (C) 2013 by Daniel Kraft +# This file is part of the Bitmessage project. +# +# 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. + +import base64 import json -from jsonrpc import ServiceProxy, JSONRPCException +import socket + +# Error thrown when the RPC call returns an error. +class RPCError (Exception): + error = None + + def __init__ (self, data): + self.error = data # This class handles the Namecoin identity integration. +class namecoinConnection (object): + user = None + password = None + host = None + port = None + bufsize = 4096 + queryid = 1 -class namecoinConnection(object): - - def __init__(self): - user = "daniel" - password = "password" - host = "localhost" - port = "8336" - self.s = ServiceProxy("http://" + user + ":" + password - + "@" + host + ":" + port) + def __init__ (self): + self.user = "daniel" + self.password = "password" + self.host = "localhost" + self.port = "8336" # Query for the bitmessage address corresponding to the given identity # string. If it doesn't contain a slash, id/ is prepended. We return # the result as (Error, Address) pair, where the Error is an error # message to display or None in case of success. - def query(self,string): - slashPos = string.find("/") + def query (self, string): + slashPos = string.find ("/") if slashPos < 0: string = "id/" + string try: - res = self.s.name_show(string) - except JSONRPCException as err: - if err.error["code"] == -4: - return ("The name '" + string + "' was not found.", None) + res = self.callRPC ("name_show", [string]) + except RPCError as exc: + if exc.error["code"] == -4: + return ("The name '%s' was not found." % string, None) else: - return ("The namecoin query failed (" - + err.error["message"] + ")", None) - except: + return ("The namecoin query failed (%s)" % exc.error["message"], + None) + except Exception as exc: + print "Namecoin query exception: %s" % str (exc) return ("The namecoin query failed.", None) try: - print res["value"] - val = json.loads(res["value"]) + val = json.loads (res["value"]) except: - return ("The name '" + string + "' has no valid JSON data.", None) + return ("The name '%s' has no valid JSON data." % string, None) if "bitmessage" in val: return (None, val["bitmessage"]) - return ("The name '" + string - + "' has no associated Bitmessage address.", None) + return ("The name '%s' has no associated Bitmessage address." % string, + None) + + # Helper routine that actually performs an JSON RPC call. + def callRPC (self, method, params): + data = {"method": method, "params": params, "id": self.queryid} + resp = self.queryHTTP (json.dumps (data)) + val = json.loads (resp) + + if val["id"] != self.queryid: + raise Exception ("ID mismatch in JSON RPC answer.") + self.queryid = self.queryid + 1 + + if val["error"] is not None: + raise RPCError (val["error"]) + + return val["result"] + + # Query the server via HTTP. + def queryHTTP (self, data): + header = "POST / HTTP/1.1\n" + header += "User-Agent: bitmessage\n" + header += "Host: %s\n" % self.host + header += "Content-Type: application/json\n" + header += "Content-Length: %d\n" % len (data) + header += "Accept: application/json\n" + authstr = "%s:%s" % (self.user, self.password) + header += "Authorization: Basic %s\n" % base64.b64encode (authstr) + + resp = self.queryServer ("%s\n%s" % (header, data)) + lines = resp.split ("\r\n") + result = None + body = False + for line in lines: + if line == "" and not body: + body = True + elif body: + if result is not None: + raise Exception ("Expected a single line in HTTP response.") + result = line + + return result + + # Helper routine sending data to the RPC server and returning the result. + def queryServer (self, data): + try: + s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.connect ((self.host, int (self.port))) + s.sendall (data) + result = "" + + while True: + tmp = s.recv (self.bufsize) + if not tmp: + break + result += tmp + + s.close () + + return result + + except socket.error as exc: + raise Exception ("Socket error in RPC connection: %s" % str (exc))