[Checkins] SVN: z3c.json/trunk/ Added initial implementation

Roger Ineichen roger at projekt01.ch
Fri Nov 30 10:50:19 EST 2007


Log message for revision 82059:
  Added initial implementation

Changed:
  A   z3c.json/trunk/CHANGES.txt
  A   z3c.json/trunk/README.txt
  A   z3c.json/trunk/TODO.txt
  A   z3c.json/trunk/bootstrap.py
  A   z3c.json/trunk/buildout.cfg
  A   z3c.json/trunk/setup.py
  A   z3c.json/trunk/src/z3c/__init__.py
  A   z3c.json/trunk/src/z3c/json/SETUP.cfg
  A   z3c.json/trunk/src/z3c/json/__init__.py
  A   z3c.json/trunk/src/z3c/json/configure.zcml
  A   z3c.json/trunk/src/z3c/json/converter.py
  A   z3c.json/trunk/src/z3c/json/exceptions.py
  A   z3c.json/trunk/src/z3c/json/interfaces.py
  A   z3c.json/trunk/src/z3c/json/minjson.py
  A   z3c.json/trunk/src/z3c/json/proxy.py
  A   z3c.json/trunk/src/z3c/json/tests.py
  A   z3c.json/trunk/src/z3c/json/transport.py
  A   z3c.json/trunk/src/z3c/json/z3c.json-configure.zcml

-=-
Added: z3c.json/trunk/CHANGES.txt
===================================================================
--- z3c.json/trunk/CHANGES.txt	                        (rev 0)
+++ z3c.json/trunk/CHANGES.txt	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,8 @@
+=======
+CHANGES
+=======
+
+Version 0.5.0 (unreleased)
+-------------------------
+
+- Initial Release


Property changes on: z3c.json/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/README.txt
===================================================================
--- z3c.json/trunk/README.txt	                        (rev 0)
+++ z3c.json/trunk/README.txt	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,4 @@
+This package provides basic JSON components like JSON reader and writer 
+utilities and a JSON-RPC client proxy including the transport implementation 
+for Zope3.
+ 
\ No newline at end of file


Property changes on: z3c.json/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/TODO.txt
===================================================================
--- z3c.json/trunk/TODO.txt	                        (rev 0)
+++ z3c.json/trunk/TODO.txt	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,8 @@
+====
+TODO
+====
+
+- add tests and use testing stub for fake the JSONRPC server response
+
+- improve coverage, write tests for all untested code, most of this is related
+  to error handling.


Property changes on: z3c.json/trunk/TODO.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/bootstrap.py
===================================================================
--- z3c.json/trunk/bootstrap.py	                        (rev 0)
+++ z3c.json/trunk/bootstrap.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 75940 2007-05-24 14:45:00Z srichter $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)


Property changes on: z3c.json/trunk/bootstrap.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/buildout.cfg
===================================================================
--- z3c.json/trunk/buildout.cfg	                        (rev 0)
+++ z3c.json/trunk/buildout.cfg	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,15 @@
+[buildout]
+develop = .
+parts = test checker coverage
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.json [test]
+
+[checker]
+recipe = lovely.recipe:importchecker
+path = src/z3c/json
+
+[coverage]
+recipe = zc.recipe.egg
+eggs = z3c.coverage

Added: z3c.json/trunk/setup.py
===================================================================
--- z3c.json/trunk/setup.py	                        (rev 0)
+++ z3c.json/trunk/setup.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup
+
+$Id:$
+"""
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup (
+    name='z3c.json',
+    version='0.5.0dev',
+    author = "Roger Ineichen and the Zope Community",
+    author_email = "zope3-dev at zope.org",
+    description = "Zope3 JSON base libraries used for z3c.jsonpage and z3c.jsonrpc",
+    long_description=(
+        read('README.txt')
+        + '\n\n' +
+        read('CHANGES.txt')
+        ),
+    license = "ZPL 2.1",
+    keywords = "zope3 z3c json base library",
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Zope Public License',
+        'Programming Language :: Python',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Topic :: Internet :: WWW/HTTP',
+        'Framework :: Zope3'],
+    url = 'http://cheeseshop.python.org/pypi/z3c.json',
+    packages = find_packages('src'),
+    include_package_data = True,
+    package_dir = {'':'src'},
+    namespace_packages = ['z3c'],
+    extras_require = dict(
+        test = [
+            'z3c.coverage',
+            'zope.app.testing',],
+        ),
+    install_requires = [
+        'setuptools',
+        'zope.component',
+        ],
+    zip_safe = False,
+)


Property changes on: z3c.json/trunk/setup.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/__init__.py
===================================================================
--- z3c.json/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/__init__.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)


Property changes on: z3c.json/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/SETUP.cfg
===================================================================
--- z3c.json/trunk/src/z3c/json/SETUP.cfg	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/SETUP.cfg	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,3 @@
+<data-files zopeskel/etc/package-includes>
+  z3c.json-*.zcml
+</data-files>

Added: z3c.json/trunk/src/z3c/json/__init__.py
===================================================================
--- z3c.json/trunk/src/z3c/json/__init__.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/__init__.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: z3c.json/trunk/src/z3c/json/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/configure.zcml
===================================================================
--- z3c.json/trunk/src/z3c/json/configure.zcml	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/configure.zcml	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,35 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    i18n_domain="z3c">
+
+  <!-- JSON reader and writer utility -->
+  <utility
+      factory=".converter.JSONReader"
+      provides=".interfaces.IJSONReader"
+      />
+
+  <utility
+      factory=".converter.JSONWriter"
+      provides=".interfaces.IJSONWriter"
+      />
+
+
+  <!-- marshaller -->
+  <adapter
+      for="list"
+      factory=".converter.ListPreMarshaller"
+      />
+
+  <adapter
+      for="tuple"
+      factory=".converter.ListPreMarshaller"
+      />
+
+  <adapter
+      factory=".converter.DictPreMarshaller"
+      for="dict"
+      />
+
+</configure>


Property changes on: z3c.json/trunk/src/z3c/json/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/converter.py
===================================================================
--- z3c.json/trunk/src/z3c/json/converter.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/converter.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,112 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+
+from z3c.json import interfaces
+from z3c.json import minjson
+from z3c.json import exceptions
+
+
+try:
+    import cjson
+    hasCJson = True
+except ImportError:
+    import logging
+    logger = logging.getLogger()
+    logger.log(logging.INFO,
+        "Using minjson only. cjson is much faster and available at the cheese "
+        "shop. easy_install python-cjson")
+    hasCJson = False
+
+
+class JSONReader(object):
+    """JSON reader utility."""
+    zope.interface.implements(interfaces.IJSONReader)
+
+    def read(self, aString, encoding=None):
+        if hasCJson:
+            try:
+                # the True parameter here tells cjson to make all strings 
+                # unicode. This is a good idea here.
+                return cjson.decode(aString, True)
+            except cjson.DecodeError:
+                # fall back to minjson
+                pass
+        # This is a fall-back position for less-well-constructed JSON
+        try:
+            return minjson.read(aString, encoding)
+        except minjson.ReadException, e:
+            raise exceptions.ResponseError(e)
+
+
+class JSONWriter(object):
+    """JSON writer utility."""
+    zope.interface.implements(interfaces.IJSONWriter)
+
+    def write(self, anObject):
+        if hasCJson:
+            try:
+                return unicode(cjson.encode(anObject))
+            except cjson.EncodeError:
+                # fall back to minjson
+                pass
+        try:
+            return minjson.write(anObject)
+        except minjson.WriteException, e:
+            raise TypeError, e
+
+
+def premarshal(data):
+    """Premarshal data before handing it to JSON writer for marshalling
+
+    The initial purpose of this function is to remove security proxies
+    without resorting to removeSecurityProxy. This way, we can avoid
+    inadvertently providing access to data that should be protected.
+    """
+    premarshaller = interfaces.IJSONRPCPremarshaller(data, alternate=None)
+    if premarshaller is not None:
+        return premarshaller()
+    return data
+
+
+class PreMarshallerBase(object):
+    """Abstract base class for pre-marshallers."""
+    zope.interface.implements(interfaces.IJSONRPCPremarshaller)
+
+    def __init__(self, data):
+        self.data = data
+
+    def __call__(self):
+        raise Exception, "Not implemented"
+
+
+class DictPreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for dicts"""
+
+    def __call__(self):
+        return dict([(premarshal(k), premarshal(v))
+                     for (k, v) in self.data.items()])
+
+
+class ListPreMarshaller(PreMarshallerBase):
+    """Pre-marshaller for list"""
+
+    def __call__(self):
+        return map(premarshal, self.data)
+


Property changes on: z3c.json/trunk/src/z3c/json/converter.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/exceptions.py
===================================================================
--- z3c.json/trunk/src/z3c/json/exceptions.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/exceptions.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,84 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+
+class Error(Exception):
+    """Base class for client errors."""
+    def __str__(self):
+        return repr(self)
+
+
+class ProtocolError(Error):
+    """Indicates an HTTP protocol error.
+
+    Indicates an HTTP-level protocol error.  This is raised by the HTTP
+    transport layer, if the server returns an error code other than 200
+    (OK).
+
+    @param url The target URL.
+    @param errcode The HTTP error code.
+    @param errmsg The HTTP error message.
+    @param headers The HTTP header dictionary.
+
+    """
+    def __init__(self, url, errcode, errmsg, headers):
+        Error.__init__(self)
+        self.url = url
+        self.errcode = errcode
+        self.errmsg = errmsg
+        self.headers = headers
+    def __repr__(self):
+        return (
+            "<ProtocolError for %s: %s %s>" %
+            (self.url, self.errcode, self.errmsg)
+            )
+
+
+class ResponseError(ValueError):
+    """Indicates a broken response package.
+
+    Indicates a broken JSON-RPC response package.  This exception is
+    raised by the unmarshalling layer, if the JSON-RPC response is
+    malformed.
+    
+    """
+    pass
+
+
+# TODO: implement this option if we implement __jsonclass__, ri
+#class Fault(Error):
+#    """Indicates an JSON-RPC fault package.
+#
+#    Indicates an JSON-RPC fault response package.  This exception is
+#    raised by the unmarshalling layer, if the JSON-RPC response contains
+#    a fault string.  This exception can also used as a class, to
+#    generate a fault JSON-RPC message.
+#
+#    @param faultCode The JSON-RPC fault code.
+#    @param faultString The JSON-RPC fault string.
+#
+#    """
+#    def __init__(self, faultCode, faultString, **extra):
+#        Error.__init__(self)
+#        self.faultCode = faultCode
+#        self.faultString = faultString
+#    def __repr__(self):
+#        return (
+#            "<Fault %s: %s>" %
+#            (self.faultCode, repr(self.faultString))
+#            )


Property changes on: z3c.json/trunk/src/z3c/json/exceptions.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/interfaces.py
===================================================================
--- z3c.json/trunk/src/z3c/json/interfaces.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/interfaces.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,42 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+
+
+class IJSONReader(zope.interface.Interface):
+    """JSON reader."""
+
+    def read(aString):
+        """read and interpret a string in JSON as python"""
+        
+
+class IJSONWriter(zope.interface.Interface):
+    """JSON writer."""
+
+    def write(anObject, encoding=None):
+        """return a JSON unicode string representation of a python object
+           Encode if encoding is provided.
+        """
+    
+
+class IJSONRPCPremarshaller(zope.interface.Interface):
+    """Premarshaller to remove security proxies"""
+
+    def __call__():
+        """return the object without proxies"""


Property changes on: z3c.json/trunk/src/z3c/json/interfaces.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/minjson.py
===================================================================
--- z3c.json/trunk/src/z3c/json/minjson.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/minjson.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,441 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+# minjson.py
+# reads minimal javascript objects.
+# str's objects and fixes the text to write javascript.
+
+#UNICODE USAGE:  Minjson tries hard to accommodate naive usage in a 
+#"Do what I mean" manner.  Real applications should handle unicode separately.
+# The "right" way to use minjson in an application is to provide minjson a 
+# python unicode string for reading and accept a unicode output from minjson's
+# writing.  That way, the assumptions for unicode are yours and not minjson's.
+
+# That said, the minjson code has some (optional) unicode handling that you 
+# may look at as a model for the unicode handling your application may need.
+
+# Thanks to Patrick Logan for starting the json-py project and making so many
+# good test cases.
+
+# Additional thanks to Balazs Ree for replacing the writing module.
+
+# Jim Washington 6 Dec 2006.
+
+# 2006-12-06 Thanks to Koen van de Sande, now handles the case where someone 
+#            might want e.g., a literal "\n" in text not a new-line.
+# 2005-12-30 writing now traverses the object tree instead of relying on 
+#            str() or unicode()
+# 2005-10-10 on reading, looks for \\uxxxx and replaces with u'\uxxxx'
+# 2005-10-09 now tries hard to make all strings unicode when reading.
+# 2005-10-07 got rid of eval() completely, makes object as found by the
+#            tokenizer.
+# 2005-09-06 imported parsing constants from tokenize; they changed a bit from
+#            python2.3 to 2.4
+# 2005-08-22 replaced the read sanity code
+# 2005-08-21 Search for exploits on eval() yielded more default bad operators.
+# 2005-08-18 Added optional code from Koen van de Sande to escape
+#            outgoing unicode chars above 128
+
+
+from re import compile, sub, search, DOTALL
+from token import ENDMARKER, NAME, NUMBER, STRING, OP, ERRORTOKEN
+from tokenize import tokenize, TokenError, NL
+
+#Usually, utf-8 will work, set this to utf-16 if you dare.
+emergencyEncoding = 'utf-8'
+
+class ReadException(Exception):
+    pass
+
+class WriteException(Exception):
+    pass
+
+#################################
+#      read JSON object         #
+#################################
+
+slashstarcomment = compile(r'/\*.*?\*/',DOTALL)
+doubleslashcomment = compile(r'//.*\n')
+
+unichrRE = compile(r"\\u[0-9a-fA-F]{4,4}")
+
+def unichrReplace(match):
+    return unichr(int(match.group()[2:],16))
+
+escapeStrs = (('\n',r'\n'),('\b',r'\b'),
+    ('\f',r'\f'),('\t',r'\t'),('\r',r'\r'), ('"',r'\"')
+    )
+
+class DictToken:
+    __slots__=[]
+    pass
+class ListToken:
+    __slots__=[]
+    pass
+class ColonToken:
+    __slots__=[]
+    pass
+class CommaToken:
+    __slots__=[]
+    pass
+
+class JSONReader(object):
+    """raise SyntaxError if it is not JSON, and make the object available"""
+    def __init__(self, data):
+        self.stop = False
+        #make an iterator of data so that next() works in tokenize.
+        self._data = iter([data])
+        self.lastOp = None
+        self.objects = []
+        self.tokenize()
+
+    def tokenize(self):
+        try:
+            tokenize(self._data.next,self.readTokens)
+        except TokenError:
+            raise SyntaxError
+
+    def resolveList(self):
+        #check for empty list
+        if isinstance(self.objects[-1],ListToken):
+            self.objects[-1] = []
+            return
+        theList = []
+        commaCount = 0
+        try:
+            item = self.objects.pop()
+        except IndexError:
+            raise SyntaxError
+        while not isinstance(item,ListToken):
+            if isinstance(item,CommaToken):
+                commaCount += 1
+            else:
+                theList.append(item)
+            try:
+                item = self.objects.pop()
+            except IndexError:
+                raise SyntaxError
+        if not commaCount == (len(theList) -1):
+            raise SyntaxError
+        theList.reverse()
+        item = theList
+        self.objects.append(item)
+
+    def resolveDict(self):
+        theList = []
+        #check for empty dict
+        if isinstance(self.objects[-1], DictToken):
+            self.objects[-1] = {}
+            return
+        #not empty; must have at least three values
+        try:
+            #value (we're going backwards!)
+            value = self.objects.pop()
+        except IndexError:
+            raise SyntaxError
+        try:
+            #colon
+            colon = self.objects.pop()
+            if not isinstance(colon, ColonToken):
+                raise SyntaxError
+        except IndexError:
+            raise SyntaxError
+        try:
+            #key
+            key = self.objects.pop()
+            if not isinstance(key,basestring):
+                raise SyntaxError
+        except IndexError:
+
+            raise SyntaxError
+        #salt the while
+        comma = value
+        while not isinstance(comma,DictToken):
+            # store the value
+            theList.append((key,value))
+            #do it again...
+            try:
+                #might be a comma
+                comma = self.objects.pop()
+            except IndexError:
+                raise SyntaxError
+            if isinstance(comma,CommaToken):
+                #if it's a comma, get the values
+                try:
+                    value = self.objects.pop()
+                except IndexError:
+                    #print self.objects
+                    raise SyntaxError
+                try:
+                    colon = self.objects.pop()
+                    if not isinstance(colon, ColonToken):
+                        raise SyntaxError
+                except IndexError:
+                    raise SyntaxError
+                try:
+                    key = self.objects.pop()
+                    if not isinstance(key,basestring):
+                        raise SyntaxError
+                except IndexError:
+                    raise SyntaxError
+        theDict = {}
+        for k in theList:
+            theDict[k[0]] = k[1]
+        self.objects.append(theDict)
+
+    def readTokens(self,type, token, (srow, scol), (erow, ecol), line):
+        # UPPERCASE consts from tokens.py or tokenize.py
+        if type == OP:
+            if token not in "[{}],:-":
+                raise SyntaxError
+            else:
+                self.lastOp = token
+            if token == '[':
+                self.objects.append(ListToken())
+            elif token == '{':
+                self.objects.append(DictToken())
+            elif token == ']':
+                self.resolveList()
+            elif token == '}':
+                self.resolveDict()
+            elif token == ':':
+                self.objects.append(ColonToken())
+            elif token == ',':
+                self.objects.append(CommaToken())
+        elif type == STRING:
+            tok = token[1:-1]
+            parts = tok.split("\\\\")
+            for k in escapeStrs:
+                if k[1] in tok:
+                    parts = [part.replace(k[1],k[0]) for part in parts]
+            self.objects.append("\\".join(parts))
+        elif type == NUMBER:
+            if self.lastOp == '-':
+                factor = -1
+            else:
+                factor = 1
+            try:
+                self.objects.append(factor * int(token))
+            except ValueError:
+                self.objects.append(factor * float(token))
+        elif type == NAME:
+            try:
+                self.objects.append({'true':True,
+                    'false':False,'null':None}[token])
+            except KeyError:
+                raise SyntaxError
+        elif type == ENDMARKER:
+            pass
+        elif type == NL:
+            pass
+        elif type == ERRORTOKEN:
+            if ecol == len(line):
+                #it's a char at the end of the line.  (mostly) harmless.
+                pass
+            else:
+                raise SyntaxError
+        else:
+            raise SyntaxError
+    def output(self):
+        try:
+            assert len(self.objects) == 1
+        except AssertionError:
+            raise SyntaxError
+        return self.objects[0]
+
+def safeRead(aString, encoding=None):
+    """read the js, first sanitizing a bit and removing any c-style comments
+    If the input is a unicode string, great.  That's preferred.  If the input 
+    is a byte string, strings in the object will be produced as unicode anyway.
+    """
+    # get rid of trailing null. Konqueror appends this.
+    CHR0 = chr(0)
+    while aString.endswith(CHR0):
+        aString = aString[:-1]
+    # strip leading and trailing whitespace
+    aString = aString.strip()
+    # zap /* ... */ comments
+    aString = slashstarcomment.sub('',aString)
+    # zap // comments
+    aString = doubleslashcomment.sub('',aString)
+    # detect and handle \\u unicode characters. Note: This has the side effect
+    # of converting the entire string to unicode. This is probably OK.
+    unicodechars = unichrRE.search(aString)
+    if unicodechars:
+        aString = unichrRE.sub(unichrReplace, aString)
+    #if it's already unicode, we won't try to decode it
+    if isinstance(aString, unicode):
+        s = aString
+    else:
+        if encoding:
+            # note: no "try" here.  the encoding provided must work for the
+            # incoming byte string.  UnicodeDecode error will be raised
+            # in that case.  Often, it will be best not to provide the encoding
+            # and allow the default
+            s = unicode(aString, encoding)
+            #print "decoded %s from %s" % (s,encoding)
+        else:
+            # let's try to decode to unicode in system default encoding
+            try:
+                s = unicode(aString)
+                #import sys
+                #print "decoded %s from %s" % (s,sys.getdefaultencoding())
+            except UnicodeDecodeError:
+                # last choice: handle as emergencyEncoding
+                enc = emergencyEncoding
+                s = unicode(aString, enc)
+                #print "%s decoded from %s" % (s, enc)
+    # parse and get the object.
+    try:
+        data = JSONReader(s).output()
+    except SyntaxError:
+        raise ReadException, 'Unacceptable JSON expression: %s' % aString
+    return data
+
+read = safeRead
+
+#################################
+#   write object as JSON        #
+#################################
+
+import re, codecs
+from cStringIO import StringIO
+
+### Codec error handler
+
+def jsonreplace_handler(exc):
+    '''Error handler for json
+
+    If encoding fails, \\uxxxx must be emitted. This
+    is similar to the "backshashreplace" handler, only
+    that we never emit \\xnn since this is not legal
+    according to the JSON syntax specs.
+    '''
+    if isinstance(exc, UnicodeEncodeError):
+        part = exc.object[exc.start]
+        # repr(part) will convert u'\unnnn' to u'u\\nnnn'
+        return u'\\u%04x' % ord(part), exc.start+1
+    else:
+        raise exc
+
+# register the error handler
+codecs.register_error('jsonreplace', jsonreplace_handler)
+
+### Writer
+
+def write(input, encoding='utf-8', outputEncoding=None):
+    writer = JsonWriter(input_encoding=encoding, output_encoding=outputEncoding)
+    writer.write(input)
+    return writer.getvalue()
+
+re_strmangle = re.compile('"|\b|\f|\n|\r|\t|\\\\')
+
+def func_strmangle(match):
+    return {
+        '"': '\\"',
+        '\b': '\\b',
+        '\f': '\\f',
+        '\n': '\\n',
+        '\r': '\\r',
+        '\t': '\\t',
+        '\\': '\\\\',
+        }[match.group(0)]
+
+def strmangle(text):
+    return re_strmangle.sub(func_strmangle, text)
+
+class JsonStream(object):
+
+    def __init__(self):
+        self.buf = []
+
+    def write(self, text):
+        self.buf.append(text)
+
+    def getvalue(self):
+        return ''.join(self.buf)
+
+class JsonWriter(object):
+
+    def __init__(self, stream=None, input_encoding='utf-8', output_encoding=None):
+        '''
+        - stream is optional, if specified must also give output_encoding
+        - The input strings can be unicode or in input_encoding
+        - output_encoding is optional, if omitted, result will be unicode
+        '''
+        if stream is not None:
+            if output_encoding is None:
+                raise WriteException, 'If a stream is given, output encoding must also be provided'
+        else:
+            stream = JsonStream()
+        self.stream = stream
+        self.input_encoding = input_encoding
+        self.output_encoding = output_encoding
+
+    def write(self, obj):
+        if isinstance(obj, (list, tuple)):
+            self.stream.write('[')
+            first = True
+            for elem in obj:
+                if first:
+                    first = False
+                else:
+                    self.stream.write(',')
+                self.write(elem)
+            self.stream.write(']'),
+        elif isinstance(obj, dict):
+            self.stream.write('{')
+            first = True
+            for key, value in obj.iteritems():
+                if first:
+                    first = False
+                else:
+                    self.stream.write(',')
+                self.write(key)
+                self.stream.write(':')
+                self.write(value)
+            self.stream.write('}')
+        elif obj is True:
+            self.stream.write('true')
+        elif obj is False:
+            self.stream.write('false')
+        elif obj is None:
+            self.stream.write('null')
+        elif not isinstance(obj, basestring):
+            # if we are not baseobj, convert to it
+            try:
+                obj = str(obj)
+            except Exception, exc:
+                raise WriteException, 'Cannot write object (%s: %s)' % (exc.__class__, exc)
+            self.stream.write(obj)
+        else:
+            # convert to unicode first
+            if not isinstance(obj, unicode):
+                try:
+                    obj = unicode(obj, self.input_encoding)
+                except (UnicodeDecodeError, UnicodeTranslateError):
+                    obj = unicode(obj, 'utf-8', 'replace')
+            # do the mangling
+            obj = strmangle(obj)
+            # make the encoding
+            if self.output_encoding is not None:
+                obj = obj.encode(self.output_encoding, 'jsonreplace')
+            self.stream.write('"')
+            self.stream.write(obj)
+            self.stream.write('"')
+
+    def getvalue(self):
+        return self.stream.getvalue()
+
+


Property changes on: z3c.json/trunk/src/z3c/json/minjson.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/proxy.py
===================================================================
--- z3c.json/trunk/src/z3c/json/proxy.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/proxy.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,136 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import string
+import urllib
+import httplib
+import copy
+import base64
+import types
+import logging
+import socket
+
+import zope.component
+from z3c.json import interfaces
+from z3c.json.exceptions import ProtocolError
+from z3c.json.exceptions import ResponseError
+
+logger = logging.getLogger(__name__)
+
+
+class _Method(object):
+    
+    def __init__(self, call, name, jsonId):
+        self.call = call
+        self.name = name
+        self.jsonId = jsonId
+    
+    def __call__(self, *args, **kwargs):
+        request = {}
+        request['version'] = '1.1'
+        request['method'] = self.name
+        if len(kwargs) is not 0:
+            params = copy.copy(kwargs)
+            index = 0
+            for arg in args:
+                params[str(index)] = arg
+                index = index + 1
+        elif len(args) is not 0:
+            params = copy.copy(args)
+        else:
+            params = {}
+        request['params'] = params
+        # add our json id
+        request['id'] = self.jsonId
+        json = zope.component.getUtility(interfaces.IJSONWriter)
+        data = json.write(request)
+        try:
+            return self.call(data)
+        except socket.error, msg:
+            raise ResponseError("JSONRPC server connection error.")
+
+    def __getattr__(self, name):
+        return _Method(self.call, "%s.%s" % (self.name, name), self.jsonId)
+
+
+class JSONRPCProxy(object):
+    """JSON-RPC server proxy."""
+
+    def __init__(self, uri, transport=None, encoding=None,
+                 verbose=None, jsonId=None):
+        utype, uri = urllib.splittype(uri)
+        if utype not in ("http", "https"):
+            raise IOError, "Unsupported JSONRPC protocol"
+        self.__host, self.__handler = urllib.splithost(uri)
+        if not self.__handler:
+            self.__handler = ""
+
+        if transport is None:
+            if utype == "https":
+                transport = SafeTransport()
+            else:
+                transport = Transport()
+        self.__transport = transport
+
+        self.__encoding = encoding
+        self.__verbose = verbose
+        self.jsonId = jsonId or u'jsonrpc'
+        self.error = None
+
+    def __request(self, request):
+        """call a method on the remote server. 
+        
+        This will raise a ResponseError or return the JSON result dict
+        """
+        # start the call
+        try:
+            response = self.__transport.request(self.__host, self.__handler,
+                request, verbose=self.__verbose)
+        except ResponseError, e:
+            # catch error message
+            self.error = unicode(e)
+            raise
+
+        if isinstance(response, int):
+            # that's just a status code response with no result
+            logger.error('Recieved status code %s' % response)
+
+        if len(response) == 3:
+            # that's a valid response format
+            if self.jsonId is not None and \
+                (self.jsonId != response.get('id')):
+                # different request id returned
+                raise ResponseError("Invalid request id returned")
+            if response.get('error'):
+                # error mesage in response
+                self.error = response['error']
+                raise ResponseError("Check proxy.error for error message")
+            else:
+                # only return the result if everything is fine
+                return response['result']
+
+        return response
+
+    def __getattr__(self, name):
+        """This let us call methods on remote server."""
+        return _Method(self.__request, name, self.jsonId)
+
+    def __repr__(self):
+        return ("<JSONProxy for %s%s>" % (self.__host, self.__handler))
+
+    __str__ = __repr__


Property changes on: z3c.json/trunk/src/z3c/json/proxy.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/tests.py
===================================================================
--- z3c.json/trunk/src/z3c/json/tests.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/tests.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,493 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+
+from z3c.json import minjson as json
+from z3c.json.minjson import ReadException
+from z3c.json.minjson import WriteException
+
+
+def spaceless(aString):
+    return aString.replace(' ','')
+
+
+class JSONTests(unittest.TestCase):
+
+    def testReadString(self):
+        s = u"'hello'"
+        self.assertEqual(json.read(s) ,'hello')
+
+    def testWriteString(self):
+        s = 'hello'
+        self.assertEqual(json.write(s), '"hello"')
+
+    def testReadInt(self):
+        s = u"1"
+        self.assertEqual(json.read(s), 1)
+
+    def testWriteInt(self):
+        s = 1
+        self.assertEqual(json.write(s), "1")
+
+    def testReadLong(self):
+        s = u"999999999999999999999"
+        self.assertEqual(json.read(s), 999999999999999999999)
+
+    def testWriteShortLong(self):
+        s = 1L
+        self.assertEqual(json.write(s), "1")
+
+    def testWriteLongLong(self):
+        s = 999999999999999999999L
+        self.assertEqual(json.write(s), "999999999999999999999")
+
+    def testReadNegInt(self):
+        s = u"-1"
+        assert json.read(s) == -1
+
+    def testWriteNegInt(self):
+        s = -1
+        assert json.write(s) == '-1'
+
+    def testReadFloat(self):
+        s = u"1.334"
+        assert json.read(s) == 1.334
+
+    def testReadEFloat1(self):
+        s = u"1.334E2"
+        assert json.read(s) == 133.4
+
+    def testReadEFloat2(self):
+        s = u"1.334E-02"
+        assert json.read(s) == 0.01334
+
+    def testReadeFloat1(self):
+        s = u"1.334e2"
+        assert json.read(s) == 133.4
+
+    def testReadeFloat2(self):
+        s = u"1.334e-02"
+        assert json.read(s) == 0.01334
+
+    def testWriteFloat(self):
+        s = 1.334
+        assert json.write(s) == "1.334"
+
+    def testWriteDecimal(self):
+        try:
+            from decimal import Decimal
+            s = Decimal('1.33')
+            assert json.write(s) == "1.33"
+        except ImportError:
+            pass
+
+    def testReadNegFloat(self):
+        s = u"-1.334"
+        assert json.read(s) == -1.334
+
+    def testWriteNegFloat(self):
+        s = -1.334
+        assert json.write(s) == "-1.334"
+
+    def testReadEmptyDict(self):
+        s = u"{}"
+        assert json.read(s) == {}
+
+    def testWriteEmptyList(self):
+        s = []
+        assert json.write(s) == "[]"
+
+    def testWriteEmptyTuple(self):
+        s = ()
+        assert json.write(s) == "[]"
+
+    def testReadEmptyList(self):
+        s = u"[]"
+        assert json.read(s) == []
+
+    def testWriteEmptyDict(self):
+        s = {}
+        assert json.write(s) == '{}'
+
+    def testReadTrue(self):
+        s = u"true"
+        assert json.read(s) == True
+
+    def testWriteTrue(self):
+        s = True
+        assert json.write(s) == "true"
+
+    def testReadStringTrue(self):
+        s = u'"true"'
+        assert json.read(s) == 'true'
+
+    def testWriteStringTrue(self):
+        s = "True"
+        assert json.write(s) == '"True"'
+
+    def testReadStringNull(self):
+        s = u'"null"'
+        assert json.read(s) == 'null'
+
+    def testWriteStringNone(self):
+        s = "None"
+        assert json.write(s) == '"None"'
+
+    def testReadFalse(self):
+        s = u"false"
+        assert json.read(s) == False
+
+    def testWriteFalse(self):
+        s = False
+        assert json.write(s) == 'false'
+
+    def testReadNull(self):
+        s = u"null"
+        assert json.read(s) == None
+
+    def testWriteNone(self):
+        s = None
+        assert json.write(s) == "null"
+
+    def testReadDictOfLists(self):
+        s = u"{'a':[],'b':[]}"
+        assert json.read(s) == {'a':[],'b':[]}
+
+    def testReadDictOfListsWithSpaces(self):
+        s = u"{  'a' :    [],  'b'  : []  }    "
+        assert json.read(s) == {'a':[],'b':[]}
+
+    def testWriteDictOfLists(self):
+        s = {'a':[],'b':[]}
+        assert spaceless(json.write(s)) == '{"a":[],"b":[]}'
+
+    def testWriteDictOfTuples(self):
+        s = {'a':(),'b':()}
+        assert spaceless(json.write(s)) == '{"a":[],"b":[]}'
+
+    def testWriteDictWithNonemptyTuples(self):
+        s = {'a':('fred',7),'b':('mary',1.234)}
+        w = json.write(s)
+        assert spaceless(w) == '{"a":["fred",7],"b":["mary",1.234]}'
+
+    def testWriteVirtualTuple(self):
+        s = 4,4,5,6
+        w = json.write(s)
+        assert spaceless(w) == '[4,4,5,6]'
+
+    def testReadListOfDicts(self):
+        s = u"[{},{}]"
+        assert json.read(s) == [{},{}]
+
+    def testReadListOfDictsWithSpaces(self):
+        s = u" [ {    } ,{   \n} ]   "
+        assert json.read(s) == [{},{}]
+
+    def testWriteListOfDicts(self):
+        s = [{},{}]
+        assert spaceless(json.write(s)) == "[{},{}]"
+
+    def testWriteTupleOfDicts(self):
+        s = ({},{})
+        assert spaceless(json.write(s)) == "[{},{}]"
+
+    def testReadListOfStrings(self):
+        s = u"['a','b','c']"
+        assert json.read(s) == ['a','b','c']
+
+    def testReadListOfStringsWithSpaces(self):
+        s = u" ['a'    ,'b'  ,\n  'c']  "
+        assert json.read(s) == ['a','b','c']
+
+    def testReadStringWithWhiteSpace(self):
+        s = ur"'hello \tworld'"
+        assert json.read(s) == 'hello \tworld'
+
+    def testWriteMixedList(self):
+        o = ['OIL',34,199L,38.5]
+        assert spaceless(json.write(o)) == '["OIL",34,199,38.5]'
+
+    def testWriteMixedTuple(self):
+        o = ('OIL',34,199L,38.5)
+        assert spaceless(json.write(o)) == '["OIL",34,199,38.5]'
+
+    def testWriteStringWithWhiteSpace(self):
+        s = 'hello \tworld'
+        assert json.write(s) == r'"hello \tworld"'
+
+    def testWriteListofStringsWithApostrophes(self):
+        s = ["hasn't","don't","isn't",True,"won't"]
+        w = json.write(s)
+        assert spaceless(w) == '["hasn\'t","don\'t","isn\'t",true,"won\'t"]'
+
+    def testWriteTupleofStringsWithApostrophes(self):
+        s = ("hasn't","don't","isn't",True,"won't")
+        w = json.write(s)
+        assert spaceless(w) == '["hasn\'t","don\'t","isn\'t",true,"won\'t"]'
+
+    def testWriteListofStringsWithRandomQuoting(self):
+        s = ["hasn't","do\"n't","isn't",True,"wo\"n't"]
+        w = json.write(s)
+        assert "true" in w
+
+    def testWriteStringWithDoubleQuote(self):
+        s = "do\"nt"
+        w = json.write(s)
+        assert w == '"do\\\"nt"'
+
+    def testReadDictWithSlashStarComments(self):
+        s = """
+        {'a':false,  /*don't want b
+            b:true, */
+        'c':true
+        }
+        """
+        assert json.read(s) == {'a':False,'c':True}
+
+    def testReadDictWithTwoSlashStarComments(self):
+        s = """
+        {'a':false,  /*don't want b
+            b:true, */
+        'c':true,
+        'd':false,  /*don;t want e
+            e:true, */
+        'f':true
+        }
+        """
+        assert json.read(s) == {'a':False,'c':True, 'd':False,'f':True}
+
+    def testReadDictWithDoubleSlashComments(self):
+        s = """
+        {'a':false,
+          //  b:true, don't want b
+        'c':true
+        }
+        """
+        assert json.read(s) == {'a':False,'c':True}
+
+    def testReadStringWithEscapedSingleQuote(self):
+        s = '"don\'t tread on me."'
+        assert json.read(s) == "don't tread on me."
+
+    def testWriteStringWithEscapedDoubleQuote(self):
+        s = 'he said, \"hi.'
+        t = json.write(s)
+        assert json.write(s) == '"he said, \\\"hi."'
+
+    def testReadStringWithEscapedDoubleQuote(self):
+        s = r'"She said, \"Hi.\""'
+        assert json.read(s) == 'She said, "Hi."'
+
+    def testReadStringWithNewLine(self):
+        s = r'"She said, \"Hi,\"\n to which he did not reply."'
+        assert json.read(s) == 'She said, "Hi,"\n to which he did not reply.'
+
+    def testReadNewLine(self):
+        s = r'"\n"'
+        assert json.read(s) == '\n'
+
+    def testWriteNewLine(self):
+        s = u'\n'
+        assert json.write(s) == r'"\n"'
+
+    def testWriteSimpleUnicode(self):
+        s = u'hello'
+        assert json.write(s) == '"hello"'
+
+    def testReadBackSlashuUnicode(self):
+        s = u'"\u0066"'
+        assert json.read(s) == 'f'
+
+    def testReadBackSlashuUnicodeInDictKey(self):
+        s = u'{"\u0066ather":34}'
+        assert json.read(s) == {'father':34}
+
+    def testReadDictKeyWithBackSlash(self):
+        s = u'{"mo\\use":22}'
+        self.assertEqual(json.read(s) , {r'mo\use':22})
+
+    def testWriteDictKeyWithBackSlash(self):
+        s = {"mo\\use":22}
+        self.assertEqual(json.write(s) , r'{"mo\\use":22}')
+
+    def testWriteListOfBackSlashuUnicodeStrings(self):
+        s = [u'\u20ac',u'\u20ac',u'\u20ac']
+        self.assertEqual(spaceless(json.write(s)) ,u'["\u20ac","\u20ac","\u20ac"]')
+
+    def testWriteUnicodeCharacter(self):
+        s = json.write(u'\u1001', 'ascii')
+        self.assertEqual(u'"\u1001"', s)
+
+    def testWriteUnicodeCharacter1(self):
+        s = json.write(u'\u1001', 'ascii',outputEncoding='ascii')
+        self.assertEqual(r'"\u1001"', s)
+
+    def testWriteHexUnicode(self):
+        s = unicode('\xff\xfe\xbf\x00Q\x00u\x00\xe9\x00 \x00p\x00a\x00s\x00a\x00?\x00','utf-16')
+        p = json.write(s, 'latin-1', outputEncoding="latin-1")
+        self.assertEqual(unicode(p,'latin-1'), u'"¿Qué pasa?"')
+
+    def testWriteHexUnicode1(self):
+        s = unicode('\xff\xfe\xbf\x00Q\x00u\x00\xe9\x00 \x00p\x00a\x00s\x00a\x00?\x00','utf-16')
+        p = json.write(s, 'latin-1')
+        self.assertEqual(p, u'"¿Qué pasa?"')
+
+    def testWriteDosPath(self):
+        s = 'c:\\windows\\system'
+        assert json.write(s) == r'"c:\\windows\\system"'
+
+    def testWriteDosPathInList(self):
+        s = ['c:\windows\system','c:\\windows\\system',r'c:\windows\system']
+        self.assertEqual(json.write(s) , r'["c:\\windows\\system","c:\\windows\\system","c:\\windows\\system"]')
+
+
+    def readImportExploit(self):
+        s = ur"\u000aimport('os').listdir('.')"
+        json.read(s)
+
+    def testImportExploit(self):
+        self.assertRaises(ReadException, self.readImportExploit)
+
+    def readClassExploit(self):
+        s = ur'''"__main__".__class__.__subclasses__()'''
+        json.read(s)
+
+    def testReadClassExploit(self):
+        self.assertRaises(ReadException, self.readClassExploit)
+
+    def readBadJson(self):
+        s = "'DOS'*30"
+        json.read(s)
+
+    def testReadBadJson(self):
+        self.assertRaises(ReadException, self.readBadJson)
+
+    def readUBadJson(self):
+        s = ur"\u0027DOS\u0027*30"
+        json.read(s)
+
+    def testReadUBadJson(self):
+        self.assertRaises(ReadException, self.readUBadJson)
+
+    def testReadEncodedUnicode(self):
+        obj = "'La Peña'"
+        r = json.read(obj, 'utf-8')
+        self.assertEqual(r, unicode('La Peña','utf-8'))
+
+    def testUnicodeFromNonUnicode(self):
+        obj = "'\u20ac'"
+        r = json.read(obj)
+        self.assertEqual(r, u'\u20ac')
+
+    def testUnicodeInObjectFromNonUnicode(self):
+        obj = "['\u20ac', '\u20acCESS', 'my\u20ACCESS']"
+        r = json.read(obj)
+        self.assertEqual(r, [u'\u20AC', u'\u20acCESS', u'my\u20acCESS'])
+
+    def testWriteWithEncoding(self):
+        obj = u'La Peña'
+        r = json.write(obj,'latin-1',outputEncoding='latin-1')
+        self.assertEqual(unicode(r,'latin-1'), u'"La Peña"')
+
+    def testWriteWithEncodingBaseCases(self):
+        #input_uni =  u"'�rvíztŹr� tßkÜrfúrógÊp'"
+        input_uni = u'\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p'
+        #print "input_uni is %s" % input_uni.encode('latin2')
+        # the result supposes doUxxxx = False
+        good_result = u'"\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p"'
+
+        # from utf8
+        obj = input_uni.encode('utf-8')
+        r = json.write(obj, 'utf-8',outputEncoding='utf-8')
+        self.assertEqual(unicode(r,'utf-8'), good_result)
+
+        # from unicode
+        obj = input_uni
+        r = json.write(obj, outputEncoding='utf-8')
+        self.assertEqual(unicode(r,'utf-8'), good_result)
+
+        # from latin2
+        obj = input_uni.encode('latin2')
+        r = json.write(obj, 'latin2', outputEncoding='latin2')
+        self.assertEqual(unicode(r,'latin2'), good_result)
+
+        # from unicode, encoding is ignored
+        obj = input_uni
+        r = json.write(obj, 'latin2', outputEncoding='latin2')
+        self.assertEqual(unicode(r,'latin2'), good_result)
+
+        # same with composite types, uni
+        good_composite_result = \
+        u'["\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p","\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p"]'
+        #print "Good composite result = %s" % good_composite_result.encode('latin2')
+        obj = [input_uni, input_uni]
+        r = json.write(obj, outputEncoding='utf-8')
+        #print "r is %s, length is %s." % (r, len(r))
+        self.assertEqual(unicode(r,'utf-8'), good_composite_result)
+
+        # same with composite types, utf-8
+        obj = [input_uni.encode('utf-8'), input_uni.encode('utf-8')]
+        r = json.write(obj, 'utf-8')
+        # print unicode(r,'utf-8'), good_composite_result
+        #self.assertEqual(unicode(r,'utf-8'), good_composite_result)
+
+        # same with composite types, latin2
+        obj = [input_uni.encode('latin2'), input_uni.encode('latin2')]
+        r = json.write(obj, 'latin2')
+        #cannot assertEqual here, but the printed representation should be readable
+        #self.assertEqual(unicode(r,'latin2'), good_composite_result)
+
+        # same with composite types, mixed utf-8 with unicode
+        obj = [input_uni, input_uni.encode('utf-8')]
+        r = json.write(obj, 'utf-8')
+        #cannot assertEqual here, but the printed representation should be readable
+        #self.assertEqual(unicode(r,'utf-8'), good_composite_result)
+    
+    #these tests from Koen van der Sande; just in case we really want literal
+    # '\n' sent across the wire.
+    
+    def testReadSpecialEscapedChars1(self):
+        test = r'"\\f"'
+        self.assertEqual([ord(x) for x in json.read(test)],[92,102])
+        
+    def testReadSpecialEscapedChars2(self):
+        test = r'"\\a"'
+        self.assertEqual([ord(x) for x in json.read(test)],[92,97])
+        
+    def testReadSpecialEscapedChars3(self):
+        test = r'"\\\\a"'
+        self.assertEqual([ord(x) for x in json.read(test)],[92,92,97])
+    
+    def testReadSpecialEscapedChars4(self):
+        test = r'"\\\\b"'
+        self.assertEqual([ord(x) for x in json.read(test)],[92,92,98])
+    
+    def testReadSpecialEscapedChars5(self):
+        test = r'"\\\n"'
+        self.assertEqual([ord(x) for x in json.read(test)],[92,10])
+
+
+def test_suite():
+    loader = unittest.TestLoader()
+    return loader.loadTestsFromTestCase(JSONTests)
+
+
+if __name__=='__main__':
+    unittest.TextTestRunner().run(test_suite())


Property changes on: z3c.json/trunk/src/z3c/json/tests.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/transport.py
===================================================================
--- z3c.json/trunk/src/z3c/json/transport.py	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/transport.py	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1,257 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import string
+import urllib
+import httplib
+import base64
+import types
+
+import zope.component
+from z3c.json import interfaces
+from z3c.json.exceptions import ProtocolError
+from z3c.json.exceptions import ResponseError
+
+
+def getparser():
+    un = Unmarshaller()
+    par = Parser(un)
+    return par,un
+
+
+class Unmarshaller(object):
+    def __init__(self):
+        self.data = None
+        
+    def feed(self, data):
+        if self.data is None:
+            self.data = data
+        else:
+            self.data = self.data + data
+
+    def close(self):
+        # convert to json, reader raises ResponseError on read error
+        json = zope.component.getUtility(interfaces.IJSONReader)
+        return json.read(self.data)
+
+
+class Parser(object):
+    def __init__(self, unmarshaller):
+        self._target = unmarshaller
+        self.data = None
+        
+    def feed(self, data):
+        if self.data is None:
+            self.data = data
+        else:
+            self.data = self.data + data
+
+    def close(self):
+        self._target.feed(self.data)
+
+
+class Transport:
+    """Handles an HTTP transaction to an JSON-RPC server.
+
+    Standard transport class for JSON-RPC over HTTP.
+    You can create custom transports by subclassing this method, and
+    overriding selected methods.
+
+    Send a complete request, and parse the response.
+
+    @param host Target host.
+    @param handler Target PRC handler.
+    @param request_body JSON-RPC request body.
+    @param verbose Debugging flag.
+    @return Parsed response.
+
+    """
+
+    user_agent = "z3c.jsonrpc/0.5.0"
+
+    def request(self, host, handler, request_body, verbose=0):
+        # issue JSON-RPC request
+
+        h = self.make_connection(host)
+        if verbose:
+            h.set_debuglevel(1)
+
+        self.send_request(h, handler, request_body)
+        self.send_host(h, host)
+        self.send_user_agent(h)
+        self.send_content(h, request_body)
+
+        errcode, errmsg, headers = h.getreply()
+
+        if errcode != 200:
+            raise ProtocolError(host + handler, errcode, errmsg, headers)
+
+        self.verbose = verbose
+
+        try:
+            sock = h._conn.sock
+        except AttributeError:
+            sock = None
+
+        return self._parse_response(h.getfile(), sock)
+
+    def getparser(self):
+        """Get parser and unmarshaller."""
+        return getparser()
+
+    def get_host_info(self, host):
+        """Get authorization info from host parameter
+
+        Host may be a string, or a (host, x509-dict) tuple; if a string,
+        it is checked for a "user:pw at host" format, and a "Basic
+        Authentication" header is added if appropriate.
+
+        @param host Host descriptor (URL or (URL, x509 info) tuple).
+        @return A 3-tuple containing (actual host, extra headers,
+        x509 info).  The header and x509 fields may be None.
+
+        """
+        x509 = {}
+        if isinstance(host, types.TupleType):
+            host, x509 = host
+
+        auth, host = urllib.splituser(host)
+
+        if auth:
+            auth = base64.encodestring(urllib.unquote(auth))
+            auth = string.join(string.split(auth), "") # get rid of whitespace
+            extra_headers = [
+                ("Authorization", "Basic " + auth)
+                ]
+        else:
+            extra_headers = None
+
+        return host, extra_headers, x509
+
+    def make_connection(self, host):
+        # create a HTTP connection object from a host descriptor
+        host, extra_headers, x509 = self.get_host_info(host)
+        return httplib.HTTP(host)
+
+    def send_request(self, connection, handler, request_body):
+        connection.putrequest("POST", handler)
+
+    def send_host(self, connection, host):
+        host, extra_headers, x509 = self.get_host_info(host)
+        connection.putheader("Host", host)
+        if extra_headers:
+            if isinstance(extra_headers, DictType):
+                extra_headers = extra_headers.items()
+            for key, value in extra_headers:
+                connection.putheader(key, value)
+
+    def send_user_agent(self, connection):
+        connection.putheader("User-Agent", self.user_agent)
+
+    def send_content(self, connection, request_body):
+        connection.putheader("Content-Type", "application/json")
+        connection.putheader("Content-Length", str(len(request_body)))
+        connection.endheaders()
+        if request_body:
+            connection.send(request_body)
+
+    def parse_response(self, file):
+        # compatibility interface
+        return self._parse_response(file, None)
+
+    def _parse_response(self, file, sock):
+        # read response from input file/socket, and parse it
+
+        p, u = self.getparser()
+
+        while 1:
+            if sock:
+                response = sock.recv(1024)
+            else:
+                response = file.read(1024)
+            if not response:
+                break
+            if self.verbose:
+                print "body:", repr(response)
+            p.feed(response)
+
+        file.close()
+        p.close()
+
+        return u.close()
+
+
+class SafeTransport(Transport):
+    """Handles an HTTPS transaction to an JSON-RPC server."""
+
+    def make_connection(self, host):
+        """Create a HTTPS connection object from a host descriptor
+
+        host may be a string, or a (host, x509-dict) tuple
+
+        """
+        host, extra_headers, x509 = self.get_host_info(host)
+        try:
+            HTTPS = httplib.HTTPS
+        except AttributeError:
+            raise NotImplementedError(
+                "your version of httplib doesn't support HTTPS"
+                )
+        else:
+            return HTTPS(host, None, **(x509 or {}))
+
+
+class BasicAuthTransport(Transport):
+
+    def __init__(self, username=None, password=None, verbose=0):
+        self.username=username
+        self.password=password
+        self.verbose=verbose
+
+    def request(self, host, handler, request_body, verbose=0):
+        # issue JSON-RPC request
+
+        self.verbose = verbose
+
+        h = httplib.HTTP(host)
+        h.putrequest("POST", handler)
+
+        # required by HTTP/1.1
+        h.putheader("Host", host)
+
+        # required by JSON-RPC
+        h.putheader("User-Agent", self.user_agent)
+        h.putheader("Content-Type", "application/json")
+        h.putheader("Content-Length", str(len(request_body)))
+
+        # basic auth
+        if self.username is not None and self.password is not None:
+            h.putheader("AUTHORIZATION", "Basic %s" %
+                base64.encodestring("%s:%s" % (self.username, self.password)
+                    ).replace("\012", ""))
+        h.endheaders()
+
+        if request_body:
+            h.send(request_body)
+
+        errcode, errmsg, headers = h.getreply()
+
+        if errcode != 200:
+            raise ProtocolError(host + handler, errcode, errmsg, headers)
+
+        return self.parse_response(h.getfile())


Property changes on: z3c.json/trunk/src/z3c/json/transport.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.json/trunk/src/z3c/json/z3c.json-configure.zcml
===================================================================
--- z3c.json/trunk/src/z3c/json/z3c.json-configure.zcml	                        (rev 0)
+++ z3c.json/trunk/src/z3c/json/z3c.json-configure.zcml	2007-11-30 15:50:19 UTC (rev 82059)
@@ -0,0 +1 @@
+<include package="z3c.json" />


Property changes on: z3c.json/trunk/src/z3c/json/z3c.json-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list