# Marc-Antoine Parent <maparent@acm.org>
# Mozilla Public License
# According to http://mah.everybody.org/docs/xmlrpc-url-scheme-rfc

import sys, urllib, urllib2, xmlrpclib,StringIO

class XmlTransport(xmlrpclib.Transport):
    """ The python XmlRpc class is too smart for my purposes, and transforms the XML return values into python objects. Here I override that behaviour."""
    def _parse_response(self, file, sock):
        buf = StringIO.StringIO()
        while 1:
            if sock:
                response = sock.recv(1024)
            else:
                response = file.read(1024)
            if not response:
                break
            buf.write(response)
        buf.seek(0)
        return buf.read()

class XmlRpcUriHandler(urllib2.AbstractHTTPHandler):
    """ This is a URI handler for the xmlrpc(s) URI schemes, proposed by
    Mark A. Hershberger
    see http://mah.everybody.org/docs/xmlrpc-url-scheme-rfc
    """
    def xmlrpcs_open(self, req):
        return self.xmlrpc_open(req)

    def xmlrpc_open(self, req):
        url = req.get_full_url()
        if ';' not in url: raise ValueError("invalid xml-rpc URL: No function name in"+url)
        (url,rpcfunc)=url.split(';',1)
        url = "http"+url[6:]
        args=[]
        if '?' in rpcfunc:
            (rpcfunc,argS)=rpcfunc.split('?',1)
            (args, argS) = self.parseArray(argS+")")
        sp = xmlrpclib.ServerProxy(url,XmlTransport())
        m = getattr(sp,rpcfunc)
        r = m(*args)
        return StringIO.StringIO(r)

    def parseArray(self, argS):
        arr=[]
        while argS:
            if argS.startswith(')'):
                return(arr,argS[1:])
            (arg, argS)=self.parseArg(argS)
            arr.append(arg)
        raise ValueError("invalid xml-rpc URL: Missing ')' in "+argS)

    def parseStruct(self, argS):
        dic={}
        while argS:
            if argS.startswith(')'):
                return(dic,argS[1:])
            if argS.startswith(','):
                argS=argS[1:]
            if '=' in argS:
                (name,argS)=argS.split('=',1)
            else:
                raise ValueError("invalid xml-rpc URL: Missing '=' in "+argS)
            (arg, argS)=self.parseArg(argS)
            dic[name]=arg

    def parseArg(self, argS):
        arg=None
        if argS.startswith(','): argS=argS[1:]
        if argS:
            if argS.startswith("array("):
                (arg, argS) = self.parseArray(argS[6:])
            elif argS.startswith("struct("):
                (arg, argS) = self.parseStruct(argS[7:])
            else:
                if ':' not in argS: raise ValueError("invalid xml-rpc URL: Missing ':' in"+argS)
                (type,argS) = argS.split(':',1)
                posComma = argS.find(",")
                posParen = argS.find(")")
                if posComma == -1 and posParen== -1: raise ValueError("invalid xml-rpc URL: type not terminated"+argS)
                if posComma == -1: posComma = sys.maxint
                if posParen == -1: posParen = sys.maxint
                pos = min(posComma,posParen)
                content = argS[:pos]
                argS = argS[pos:]
                if type == "string":
                    arg=str(urllib.unquote_plus(content))
                elif type == "int":
                    arg=int(content)
                elif type == "boolean" :
                    arg=bool(content)
                elif type == "double" :
                    arg=float(content)
                elif type == "base64" :
                    arg=xmlrpclib.Binary()
                    arg.decode(content)
                elif type == "dateTime.iso8601":
                    arg=xmlrpclib.DateTime()
                    arg.decode(content)
                else:
                    raise ValueError("invalid xml-rpc URL: Unknown type"+type)
        return (arg, argS)

if urllib2._opener is None:
    urllib2._opener = urllib2.build_opener()
urllib2._opener.add_handler(XmlRpcUriHandler())

if __name__ == '__main__':
    s='struct(a=string:C6CE3FFB3174106584CBB250C0B0519BF4E294,b=int:744145),string:ewilliams,string:secret,string:Today%20I%20had%20a%20peanut%20butter%20and%20pickle%20sandwich%20for%20lunch.%20Do%20you%20like%20peanut-butter%20and%20pickle%20sandwiches?%20I%20do.%20They\'re%20yummy.%20Please%20comment!,boolean:false)'
    i= XmlRpcUriHandler()
    print i.parseArray(s)
    urls = (
        'xmlrpc://time.userland.com/RPC2;currentTime.getCurrentTime',
        'xmlrpc://www.syndic8.com/xmlrpc.php;syndic8.FindFeeds?string:mah.everybody.org'
        )
    for url in urls:
        print "reading",url
        f = urllib2.urlopen(url)
        print f.read()
