starting new development to support slice conf files
authorTony Mack <tmack@cs.princeton.edu>
Fri, 10 Oct 2008 19:42:41 +0000 (19:42 +0000)
committerTony Mack <tmack@cs.princeton.edu>
Fri, 10 Oct 2008 19:42:41 +0000 (19:42 +0000)
97 files changed:
php/.cvsignore [new file with mode: 0644]
php/Makefile [new file with mode: 0644]
php/footer.php [new file with mode: 0644]
php/header.php [new file with mode: 0644]
php/methods.py [new file with mode: 0755]
php/xmlrpc/CREDITS [new file with mode: 0644]
php/xmlrpc/EXPERIMENTAL [new file with mode: 0644]
php/xmlrpc/Makefile [new file with mode: 0644]
php/xmlrpc/config.m4 [new file with mode: 0644]
php/xmlrpc/config.m4.lib64 [new file with mode: 0644]
php/xmlrpc/config.w32 [new file with mode: 0644]
php/xmlrpc/libxmlrpc/README [new file with mode: 0644]
php/xmlrpc/libxmlrpc/acinclude.m4 [new file with mode: 0644]
php/xmlrpc/libxmlrpc/base64.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/base64.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/encodings.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/encodings.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/queue.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/queue.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/simplestring.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/simplestring.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/system_methods.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/system_methods_private.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_element.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_element.c.gcc4 [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_element.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_to_dandarpc.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_to_dandarpc.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_to_soap.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_to_soap.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_to_xmlrpc.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xml_to_xmlrpc.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc.m4 [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc_introspection.c [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc_introspection.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc_introspection_private.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc_private.h [new file with mode: 0644]
php/xmlrpc/libxmlrpc/xmlrpc_win32.h [new file with mode: 0644]
php/xmlrpc/php_xmlrpc.h [new file with mode: 0644]
php/xmlrpc/xmlrpc-epi-php.c [new file with mode: 0644]
php/xmlrpc/xmlrpc.dsp [new file with mode: 0644]
psycopg2/AUTHORS [new file with mode: 0644]
psycopg2/LICENSE [new file with mode: 0644]
psycopg2/MANIFEST.in [new file with mode: 0644]
psycopg2/doc/ChangeLog-1.x [new file with mode: 0644]
psycopg2/doc/SUCCESS [new file with mode: 0644]
psycopg2/doc/TODO [new file with mode: 0644]
psycopg2/doc/api-screen.css [new file with mode: 0644]
psycopg2/doc/async.txt [new file with mode: 0644]
psycopg2/doc/extensions.html [new file with mode: 0644]
psycopg2/doc/extensions.rst [new file with mode: 0644]
pycurl/COPYING [new file with mode: 0644]
pycurl/ChangeLog [new file with mode: 0644]
pycurl/INSTALL [new file with mode: 0644]
pycurl/MANIFEST.in [new file with mode: 0644]
pycurl/Makefile [new file with mode: 0644]
pycurl/PKG-INFO [new file with mode: 0644]
pycurl/README [new file with mode: 0644]
pycurl/TODO [new file with mode: 0644]
pycurl/doc/callbacks.html [new file with mode: 0644]
pycurl/doc/curlmultiobject.html [new file with mode: 0644]
pycurl/doc/curlobject.html [new file with mode: 0644]
pycurl/doc/pycurl.html [new file with mode: 0644]
pycurl/examples/basicfirst.py [new file with mode: 0644]
pycurl/examples/file_upload.py [new file with mode: 0644]
pycurl/examples/linksys.py [new file with mode: 0755]
pycurl/examples/retriever-multi.py [new file with mode: 0644]
pycurl/examples/retriever.py [new file with mode: 0644]
pycurl/examples/sfquery.py [new file with mode: 0644]
pycurl/examples/xmlrpc_curl.py [new file with mode: 0644]
pycurl/python/curl/__init__.py [new file with mode: 0644]
pycurl/setup.py [new file with mode: 0644]
pycurl/setup_win32_ssl.py [new file with mode: 0644]
pycurl/src/Makefile [new file with mode: 0644]
pycurl/src/pycurl.c [new file with mode: 0644]
pycurl/tests/test.py [new file with mode: 0644]
pycurl/tests/test_cb.py [new file with mode: 0644]
pycurl/tests/test_debug.py [new file with mode: 0644]
pycurl/tests/test_getinfo.py [new file with mode: 0644]
pycurl/tests/test_gtk.py [new file with mode: 0644]
pycurl/tests/test_internals.py [new file with mode: 0644]
pycurl/tests/test_memleak.py [new file with mode: 0644]
pycurl/tests/test_multi.py [new file with mode: 0644]
pycurl/tests/test_multi2.py [new file with mode: 0644]
pycurl/tests/test_multi3.py [new file with mode: 0644]
pycurl/tests/test_multi4.py [new file with mode: 0644]
pycurl/tests/test_multi5.py [new file with mode: 0644]
pycurl/tests/test_multi6.py [new file with mode: 0644]
pycurl/tests/test_multi_vs_thread.py [new file with mode: 0644]
pycurl/tests/test_post.py [new file with mode: 0644]
pycurl/tests/test_post2.py [new file with mode: 0644]
pycurl/tests/test_post3.py [new file with mode: 0644]
pycurl/tests/test_stringio.py [new file with mode: 0644]
pycurl/tests/test_xmlrpc.py [new file with mode: 0644]
pycurl/tests/util.py [new file with mode: 0644]

diff --git a/php/.cvsignore b/php/.cvsignore
new file mode 100644 (file)
index 0000000..dd89eef
--- /dev/null
@@ -0,0 +1,2 @@
+methods.php
+plc_api.php
diff --git a/php/Makefile b/php/Makefile
new file mode 100644 (file)
index 0000000..d2eeb3f
--- /dev/null
@@ -0,0 +1,25 @@
+#
+# (Re)builds PHP API. PHP classes must be defined in a single file.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Makefile 5574 2007-10-25 20:33:17Z thierry $
+#
+
+all: plc_api.php
+
+methods.php: methods.py ../PLC/__init__.py ../PLC/Methods/__init__.py
+       PYTHONPATH=.. python $< > $@
+        # Indent all lines by a couple of spaces
+       sed -i -e "s/^/  /" $@
+
+plc_api.php: header.php methods.php footer.php
+        # Set timestamp
+       sed -e "s/@DATE@/$$(date)/" header.php > $@
+       cat methods.php footer.php >> $@
+
+clean:
+       rm -f plc_api.php methods.php
+
+.PHONY: all clean
diff --git a/php/footer.php b/php/footer.php
new file mode 100644 (file)
index 0000000..6212304
--- /dev/null
@@ -0,0 +1,9 @@
+}
+
+global $adm;
+
+$adm = new PLCAPI(array('AuthMethod' => "capability",
+                       'Username' => PLC_API_MAINTENANCE_USER,
+                       'AuthString' => PLC_API_MAINTENANCE_PASSWORD));
+
+?>
diff --git a/php/header.php b/php/header.php
new file mode 100644 (file)
index 0000000..6465798
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+//
+// PlanetLab Central Slice API (PLCAPI) PHP interface
+//
+// DO NOT EDIT. This file was automatically generated at
+// @DATE@.
+//
+// Mark Huang <mlhuang@cs.princeton.edu>
+// Copyright (C) 2005-2006 The Trustees of Princeton University
+//
+// $Id: header.php 5574 2007-10-25 20:33:17Z thierry $
+//
+//
+
+require_once 'plc_config.php';
+
+class PLCAPI
+{
+  var $auth;
+  var $server;
+  var $port;
+  var $path;
+  var $errors;
+  var $trace;
+  var $calls;
+  var $multicall;
+
+  function PLCAPI($auth = NULL,
+                 $server = PLC_API_HOST,
+                 $port = PLC_API_PORT,
+                 $path = PLC_API_PATH,
+                 $cainfo = NULL)
+  {
+    $this->auth = $auth;
+    $this->server = $server;
+    $this->port = $port;
+    $this->path = $path;
+    $this->cainfo = $cainfo;
+    $this->errors = array();
+    $this->trace = array();
+    $this->calls = array();
+    $this->multicall = false;
+  }
+
+  function error_log($error_msg, $backtrace_level = 1)
+  {
+    $backtrace = debug_backtrace();
+    $file = $backtrace[$backtrace_level]['file'];
+    $line = $backtrace[$backtrace_level]['line'];
+
+    $this->errors[] = 'PLCAPI error:  ' . $error_msg . ' in ' . $file . ' on line ' . $line;
+    error_log(end($this->errors));
+  }
+
+  function error()
+  {
+    if (empty($this->trace)) {
+      return NULL;
+    } else {
+      $last_trace = end($this->trace);
+      return implode("\\n", $last_trace['errors']);
+    }
+  }
+
+  function trace()
+  {
+    return $this->trace;
+  }
+
+  function microtime_float()
+  {
+    list($usec, $sec) = explode(" ", microtime());
+    return ((float) $usec + (float) $sec);
+  }
+
+  function call($method, $args = NULL)
+  {
+    if ($this->multicall) {
+      $this->calls[] = array ('methodName' => $method,
+                               'params' => $args);
+      return NULL;
+    } else {
+      return $this->internal_call ($method, $args, 3);
+    }
+  }
+
+  function internal_call($method, $args = NULL, $backtrace_level = 2)
+  {
+    $curl = curl_init();
+
+    // Verify peer certificate if talking over SSL
+    if ($this->port == 443) {
+      curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
+      if (!empty($this->cainfo)) {
+       curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
+      } elseif (defined('PLC_API_CA_SSL_CRT')) {
+        curl_setopt($curl, CURLOPT_CAINFO, PLC_API_CA_SSL_CRT);
+      }
+      $url = 'https://';
+    } else {
+      $url = 'http://';
+    }
+
+    // Set the URL for the request
+    $url .= $this->server . ':' . $this->port . '/' . $this->path;
+    curl_setopt($curl, CURLOPT_URL, $url);
+
+    // Marshal the XML-RPC request as a POST variable. <nil/> is an
+    // extension to the XML-RPC spec that is supported in our custom
+    // version of xmlrpc.so via the 'allow_null' output_encoding key.
+    $request = xmlrpc_encode_request($method, $args, array('allow_null' => TRUE));
+    curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
+
+    // Construct the HTTP header
+    $header[] = 'Content-type: text/xml';
+    $header[] = 'Content-length: ' . strlen($request);
+    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
+
+    // Set some miscellaneous options
+    curl_setopt($curl, CURLOPT_TIMEOUT, 30);
+
+    // Get the output of the request
+    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+    $t0 = $this->microtime_float();
+    $output = curl_exec($curl);
+    $t1 = $this->microtime_float();
+
+    if (curl_errno($curl)) {
+      $this->error_log('curl: ' . curl_error($curl), true);
+      $ret = NULL;
+    } else {
+      $ret = xmlrpc_decode($output);
+      if (is_array($ret) && xmlrpc_is_fault($ret)) {
+        $this->error_log('Fault Code ' . $ret['faultCode'] . ': ' .
+                         $ret['faultString'], $backtrace_level, true);
+       $ret = NULL;
+      }
+    }
+
+    curl_close($curl);
+
+    $this->trace[] = array('method' => $method,
+                           'args' => $args,
+                           'runtime' => $t1 - $t0,
+                           'return' => $ret,
+                           'errors' => $this->errors);
+    $this->errors = array();
+
+    return $ret;
+  }
+
+  function begin()
+  {
+    if (!empty($this->calls)) {
+      $this->error_log ('Warning: multicall already in progress');
+    }
+
+    $this->multicall = true;
+  }
+
+  function commit()
+  {
+    if (!empty ($this->calls)) {
+      $ret = array();
+      $results = $this->internal_call ('system.multicall', array ($this->calls));
+      foreach ($results as $result) {
+        if (is_array($result)) {
+          if (xmlrpc_is_fault($result)) {
+            $this->error_log('Fault Code ' . $result['faultCode'] . ': ' .
+                             $result['faultString'], 1, true);
+            $ret[] = NULL;
+           // Thierry - march 30 2007 
+           // using $adm->error() is broken with begin/commit style 
+           // this is because error() uses last item in trace and checks for ['errors']
+           // when using begin/commit we do run internal_call BUT internal_call checks for 
+           // multicall's result globally, not individual results, so ['errors'] comes empty
+           // I considered hacking internal_call 
+           // to *NOT* maintain this->trace at all when invoked with multicall
+           // but it is too complex to get all values right
+           // so let's go for the hacky way, and just record individual errors at the right place
+            $this->trace[count($this->trace)-1]['errors'][] = end($this->errors);
+          } else {
+            $ret[] = $result[0];
+          }
+        } else {
+          $ret[] = $result;
+        }
+      }
+    } else {
+      $ret = NULL;
+    }
+
+    $this->calls = array();
+    $this->multicall = false;
+
+    return $ret;
+  }
+
+  //
+  // PLCAPI Methods
+  //
+
diff --git a/php/methods.py b/php/methods.py
new file mode 100755 (executable)
index 0000000..ad7944f
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/python
+#
+# Generates the PLCAPI interface for the website PHP code.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2005 The Trustess of Princeton University
+#
+# $Id: methods.py 5574 2007-10-25 20:33:17Z thierry $
+#
+
+import os, sys
+import time
+
+from PLC.API import PLCAPI
+from PLC.Method import *
+from PLC.Auth import Auth
+
+try:
+    set
+except NameError:
+    from sets import Set
+    set = Set
+
+def php_cast(value):
+    """
+    Casts Python values to PHP values.
+    """
+    
+    if value is None:
+        return "NULL"
+    elif isinstance(value, (list, tuple, set)):
+        return "array(%s)" % ", ".join([php_cast(v) for v in value])
+    elif isinstance(value, dict):
+        items = ["%s => %s" % (php_cast(k), php_cast(v)) for (k, v) in value.items()]
+        return "array(%s)" % ", ".join(items)
+    elif isinstance(value, (int, long, bool, float)):
+        return str(value)
+    else:
+        unicode_repr = repr(unicode(value))
+        # Truncate the leading 'u' prefix
+        return unicode_repr[1:]
+
+# Class functions
+api = PLCAPI(None)
+
+api.methods.sort()
+for method in api.methods:
+    # Skip system. methods
+    if "system." in method:
+        continue
+
+    function = api.callable(method)
+
+    # Commented documentation
+    lines = ["// " + line.strip() for line in function.__doc__.strip().split("\n")]
+    print "\n".join(lines)
+    print
+
+    # Function declaration
+    print "function " + function.name,
+
+    # PHP function arguments
+    args = []
+    (min_args, max_args, defaults) = function.args()
+    parameters = zip(max_args, function.accepts, defaults)
+
+    for name, expected, default in parameters:
+        # Skip auth structures (added automatically)
+        if isinstance(expected, Auth) or \
+           (isinstance(expected, Mixed) and \
+            filter(lambda sub: isinstance(sub, Auth), expected)):
+            continue
+
+        # Declare parameter
+        arg = "$" + name
+
+        # Set optional parameters to their defaults
+        if name not in min_args:
+            arg += " = " + php_cast(default)
+
+        args.append(arg)
+
+    # Write function declaration
+    print "(" + ", ".join(args) + ")"
+
+    # Begin function body
+    print "{"
+
+    # API function arguments
+    i = 0
+    for name, expected, default in parameters:
+        # Automatically added auth structures
+        if isinstance(expected, Auth) or \
+           (isinstance(expected, Mixed) and \
+            filter(lambda sub: isinstance(sub, Auth), expected)):
+            print "  $args[] = $this->auth;"
+            continue
+
+        print " ",
+        if name not in min_args:
+            print "if (func_num_args() > %d)" % i, 
+        print "$args[] = $%s;" % name
+
+        i += 1
+
+    # Call API function
+    print "  return $this->call('%s', $args);" % method
+
+    # End function body
+    print "}"
+    print
diff --git a/php/xmlrpc/CREDITS b/php/xmlrpc/CREDITS
new file mode 100644 (file)
index 0000000..cfb14fa
--- /dev/null
@@ -0,0 +1,2 @@
+xmlrpc
+Dan Libby
diff --git a/php/xmlrpc/EXPERIMENTAL b/php/xmlrpc/EXPERIMENTAL
new file mode 100644 (file)
index 0000000..6443e99
--- /dev/null
@@ -0,0 +1,5 @@
+this extension is experimental,
+its functions may change their names 
+or move to extension all together 
+so do not rely to much on them 
+you have been warned!
diff --git a/php/xmlrpc/Makefile b/php/xmlrpc/Makefile
new file mode 100644 (file)
index 0000000..e4fea32
--- /dev/null
@@ -0,0 +1,32 @@
+#
+# Build xmlrpc.so PHP extension
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id: Makefile 7510 2007-12-13 12:32:09Z thierry $
+#
+
+CC := gcc
+CFLAGS := -g -O2 -I. -Ilibxmlrpc -fPIC
+CFLAGS += $(shell php-config --includes)
+CFLAGS += $(shell xml2-config --cflags)
+
+LDFLAGS := -shared --export-dynamic
+LIBS := -lexpat
+
+# for building on fedora 8
+ifneq "$(shell ld --help | grep build-id)" ""
+#not needed# CFLAGS += -Wl,--build-id
+LD += --build-id
+endif
+
+all: xmlrpc.so
+
+xmlrpc.so: xmlrpc-epi-php.o $(patsubst %.c, %.o, $(wildcard libxmlrpc/*.c))
+       $(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+clean:
+       rm -f *.o libxmlrpc/*.o *.so
+
+.PHONY: all clean
diff --git a/php/xmlrpc/config.m4 b/php/xmlrpc/config.m4
new file mode 100644 (file)
index 0000000..5a1a1e8
--- /dev/null
@@ -0,0 +1,93 @@
+dnl
+dnl $Id: config.m4 5574 2007-10-25 20:33:17Z thierry $
+dnl
+
+sinclude(ext/xmlrpc/libxmlrpc/acinclude.m4)
+sinclude(ext/xmlrpc/libxmlrpc/xmlrpc.m4)
+sinclude(libxmlrpc/acinclude.m4)
+sinclude(libxmlrpc/xmlrpc.m4)
+
+PHP_ARG_WITH(xmlrpc, for XMLRPC-EPI support,
+[  --with-xmlrpc[=DIR]     Include XMLRPC-EPI support.])
+
+PHP_ARG_WITH(expat-dir, libexpat dir for XMLRPC-EPI,
+[  --with-expat-dir=DIR      XMLRPC-EPI: libexpat dir for XMLRPC-EPI.],no,no)
+
+PHP_ARG_WITH(iconv-dir, iconv dir for XMLRPC-EPI,
+[  --with-iconv-dir=DIR      XMLRPC-EPI: iconv dir for XMLRPC-EPI.],no,no)
+
+if test "$PHP_XMLRPC" != "no"; then
+
+  PHP_SUBST(XMLRPC_SHARED_LIBADD)
+  AC_DEFINE(HAVE_XMLRPC,1,[ ])
+
+  testval=no
+  for i in $PHP_EXPAT_DIR $XMLRPC_DIR /usr/local /usr; do
+    if test -f $i/$PHP_LIBDIR/libexpat.a -o -f $i/$PHP_LIBDIR/libexpat.$SHLIB_SUFFIX_NAME; then
+      AC_DEFINE(HAVE_LIBEXPAT2,1,[ ])
+      PHP_ADD_LIBRARY_WITH_PATH(expat, $i/$PHP_LIBDIR, XMLRPC_SHARED_LIBADD)
+      PHP_ADD_INCLUDE($i/include)
+      testval=yes
+      break
+    fi
+  done
+
+  if test "$testval" = "no"; then
+    AC_MSG_ERROR(XML-RPC support requires libexpat. Use --with-expat-dir=<DIR>)
+  fi
+
+  if test "$PHP_ICONV_DIR" != "no"; then
+    PHP_ICONV=$PHP_ICONV_DIR
+  fi
+  
+  if test "$PHP_ICONV" = "no"; then
+    PHP_ICONV=yes
+  fi
+  
+  PHP_SETUP_ICONV(XMLRPC_SHARED_LIBADD, [], [
+    AC_MSG_ERROR([iconv not found, in order to build xmlrpc you need the iconv library])
+  ])
+fi
+
+
+if test "$PHP_XMLRPC" = "yes"; then
+  XMLRPC_CHECKS
+  PHP_NEW_EXTENSION(xmlrpc,xmlrpc-epi-php.c libxmlrpc/base64.c \
+          libxmlrpc/simplestring.c libxmlrpc/xml_to_dandarpc.c \
+          libxmlrpc/xmlrpc_introspection.c libxmlrpc/encodings.c \
+          libxmlrpc/system_methods.c libxmlrpc/xml_to_xmlrpc.c \
+          libxmlrpc/queue.c libxmlrpc/xml_element.c libxmlrpc/xmlrpc.c \
+          libxmlrpc/xml_to_soap.c,$ext_shared,,
+          -I@ext_srcdir@/libxmlrpc -DVERSION="0.50")
+  PHP_ADD_BUILD_DIR($ext_builddir/libxmlrpc)
+  XMLRPC_MODULE_TYPE=builtin
+
+elif test "$PHP_XMLRPC" != "no"; then
+
+  if test -r $PHP_XMLRPC/include/xmlrpc.h; then
+    XMLRPC_DIR=$PHP_XMLRPC/include
+  elif test -r $PHP_XMLRPC/include/xmlrpc-epi/xmlrpc.h; then
+dnl some xmlrpc-epi header files have generic file names like
+dnl queue.h or base64.h. Distributions have to create dir
+dnl for xmlrpc-epi because of this.
+    XMLRPC_DIR=$PHP_XMLRPC/include/xmlrpc-epi
+  else
+    AC_MSG_CHECKING(for XMLRPC-EPI in default path)
+    for i in /usr/local /usr; do
+      if test -r $i/include/xmlrpc.h; then
+        XMLRPC_DIR=$i/include
+        AC_MSG_RESULT(found in $i)
+        break
+      fi
+    done
+  fi
+
+  if test -z "$XMLRPC_DIR"; then
+    AC_MSG_RESULT(not found)
+    AC_MSG_ERROR(Please reinstall the XMLRPC-EPI distribution)
+  fi
+
+  PHP_ADD_INCLUDE($XMLRPC_DIR)
+  PHP_ADD_LIBRARY_WITH_PATH(xmlrpc, $XMLRPC_DIR/$PHP_LIBDIR, XMLRPC_SHARED_LIBADD)
+fi
+
diff --git a/php/xmlrpc/config.m4.lib64 b/php/xmlrpc/config.m4.lib64
new file mode 100644 (file)
index 0000000..ff54f89
--- /dev/null
@@ -0,0 +1,93 @@
+dnl
+dnl $Id: config.m4.lib64 5574 2007-10-25 20:33:17Z thierry $
+dnl
+
+sinclude(ext/xmlrpc/libxmlrpc/acinclude.m4)
+sinclude(ext/xmlrpc/libxmlrpc/xmlrpc.m4)
+sinclude(libxmlrpc/acinclude.m4)
+sinclude(libxmlrpc/xmlrpc.m4)
+
+PHP_ARG_WITH(xmlrpc, for XMLRPC-EPI support,
+[  --with-xmlrpc[=DIR]     Include XMLRPC-EPI support.])
+
+PHP_ARG_WITH(expat-dir, libexpat dir for XMLRPC-EPI,
+[  --with-expat-dir=DIR      XMLRPC-EPI: libexpat dir for XMLRPC-EPI.],no,no)
+
+PHP_ARG_WITH(iconv-dir, iconv dir for XMLRPC-EPI,
+[  --with-iconv-dir=DIR      XMLRPC-EPI: iconv dir for XMLRPC-EPI.],no,no)
+
+if test "$PHP_XMLRPC" != "no"; then
+
+  PHP_SUBST(XMLRPC_SHARED_LIBADD)
+  AC_DEFINE(HAVE_XMLRPC,1,[ ])
+
+  testval=no
+  for i in $PHP_EXPAT_DIR $XMLRPC_DIR /usr/local /usr; do
+    if test -f $i/lib/libexpat.a -o -f $i/lib/libexpat.$SHLIB_SUFFIX_NAME; then
+      AC_DEFINE(HAVE_LIBEXPAT2,1,[ ])
+      PHP_ADD_LIBRARY_WITH_PATH(expat, $i/lib, XMLRPC_SHARED_LIBADD)
+      PHP_ADD_INCLUDE($i/include)
+      testval=yes
+      break
+    fi
+  done
+
+  if test "$testval" = "no"; then
+    AC_MSG_ERROR(XML-RPC support requires libexpat. Use --with-expat-dir=<DIR>)
+  fi
+
+  if test "$PHP_ICONV_DIR" != "no"; then
+    PHP_ICONV=$PHP_ICONV_DIR
+  fi
+  
+  if test "$PHP_ICONV" = "no"; then
+    PHP_ICONV=yes
+  fi
+  
+  PHP_SETUP_ICONV(XMLRPC_SHARED_LIBADD, [], [
+    AC_MSG_ERROR([iconv not found, in order to build xmlrpc you need the iconv library])
+  ])
+fi
+
+
+if test "$PHP_XMLRPC" = "yes"; then
+  XMLRPC_CHECKS
+  PHP_NEW_EXTENSION(xmlrpc,xmlrpc-epi-php.c libxmlrpc/base64.c \
+          libxmlrpc/simplestring.c libxmlrpc/xml_to_dandarpc.c \
+          libxmlrpc/xmlrpc_introspection.c libxmlrpc/encodings.c \
+          libxmlrpc/system_methods.c libxmlrpc/xml_to_xmlrpc.c \
+          libxmlrpc/queue.c libxmlrpc/xml_element.c libxmlrpc/xmlrpc.c \
+          libxmlrpc/xml_to_soap.c,$ext_shared,,
+          -I@ext_srcdir@/libxmlrpc -DVERSION="0.50")
+  PHP_ADD_BUILD_DIR($ext_builddir/libxmlrpc)
+  XMLRPC_MODULE_TYPE=builtin
+
+elif test "$PHP_XMLRPC" != "no"; then
+
+  if test -r $PHP_XMLRPC/include/xmlrpc.h; then
+    XMLRPC_DIR=$PHP_XMLRPC/include
+  elif test -r $PHP_XMLRPC/include/xmlrpc-epi/xmlrpc.h; then
+dnl some xmlrpc-epi header files have generic file names like
+dnl queue.h or base64.h. Distributions have to create dir
+dnl for xmlrpc-epi because of this.
+    XMLRPC_DIR=$PHP_XMLRPC/include/xmlrpc-epi
+  else
+    AC_MSG_CHECKING(for XMLRPC-EPI in default path)
+    for i in /usr/local /usr; do
+      if test -r $i/include/xmlrpc.h; then
+        XMLRPC_DIR=$i/include
+        AC_MSG_RESULT(found in $i)
+        break
+      fi
+    done
+  fi
+
+  if test -z "$XMLRPC_DIR"; then
+    AC_MSG_RESULT(not found)
+    AC_MSG_ERROR(Please reinstall the XMLRPC-EPI distribution)
+  fi
+
+  PHP_ADD_INCLUDE($XMLRPC_DIR)
+  PHP_ADD_LIBRARY_WITH_PATH(xmlrpc, $XMLRPC_DIR/lib, XMLRPC_SHARED_LIBADD)
+fi
+
diff --git a/php/xmlrpc/config.w32 b/php/xmlrpc/config.w32
new file mode 100644 (file)
index 0000000..0f6bf0c
--- /dev/null
@@ -0,0 +1,14 @@
+// $Id: config.w32 5574 2007-10-25 20:33:17Z thierry $
+// vim:ft=javascript
+
+ARG_WITH("xmlrpc", "XMLRPC-EPI support", "no");
+
+if (PHP_XMLRPC != "no") {
+       CHECK_HEADER_ADD_INCLUDE("xmlrpc.h", "CFLAGS_XMLRPC", configure_module_dirname + "/libxmlrpc");
+       EXTENSION('xmlrpc', 'xmlrpc-epi-php.c', PHP_XMLRPC_SHARED, "-DVERSION=\"0.50\"");
+       ADD_SOURCES(configure_module_dirname + "/libxmlrpc", "base64.c simplestring.c xml_to_dandarpc.c \
+       xmlrpc_introspection.c encodings.c system_methods.c xml_to_xmlrpc.c \
+       queue.c xml_element.c xmlrpc.c xml_to_soap.c", "xmlrpc");
+       ADD_EXTENSION_DEP('xmlrpc', 'libxml');
+}
+
diff --git a/php/xmlrpc/libxmlrpc/README b/php/xmlrpc/libxmlrpc/README
new file mode 100644 (file)
index 0000000..323edfa
--- /dev/null
@@ -0,0 +1,17 @@
+organization of this directory is moving towards this approach:
+
+<module>.h               -- public API and data types
+<module>_private.h       -- protected API and data types
+<module>.c               -- implementation and private API / types
+
+The rules are:
+.c files may include *_private.h.
+.h files may not include *_private.h
+
+This allows us to have a nicely encapsulated C api with opaque data types and private functions
+that are nonetheless shared between source files without redundant extern declarations..
+
+
+
+
+
diff --git a/php/xmlrpc/libxmlrpc/acinclude.m4 b/php/xmlrpc/libxmlrpc/acinclude.m4
new file mode 100644 (file)
index 0000000..49b6090
--- /dev/null
@@ -0,0 +1,32 @@
+# Local macros for automake & autoconf
+
+AC_DEFUN([XMLRPC_FUNCTION_CHECKS],[
+
+# Standard XMLRPC list
+AC_CHECK_FUNCS( \
+ strtoul strtoull snprintf \
+ strstr strpbrk strerror\
+ memcpy memmove)
+
+])
+
+AC_DEFUN([XMLRPC_HEADER_CHECKS],[
+AC_HEADER_STDC
+AC_CHECK_HEADERS(xmlparse.h xmltok.h stdlib.h strings.h string.h)
+])
+
+AC_DEFUN([XMLRPC_TYPE_CHECKS],[
+
+AC_REQUIRE([AC_C_CONST])
+AC_REQUIRE([AC_C_INLINE])
+AC_CHECK_SIZEOF(char, 1)
+
+AC_CHECK_SIZEOF(int, 4)
+AC_CHECK_SIZEOF(long, 4)
+AC_CHECK_SIZEOF(long long, 8)
+AC_TYPE_SIZE_T
+AC_HEADER_TIME
+AC_TYPE_UID_T
+
+
+])
diff --git a/php/xmlrpc/libxmlrpc/base64.c b/php/xmlrpc/libxmlrpc/base64.c
new file mode 100644 (file)
index 0000000..b90536f
--- /dev/null
@@ -0,0 +1,192 @@
+static const char rcsid[] = "#(@) $Id: base64.c 5574 2007-10-25 20:33:17Z thierry $";
+
+/*
+
+          Encode or decode file as MIME base64 (RFC 1341)
+
+                           by John Walker
+                      http://www.fourmilab.ch/
+
+               This program is in the public domain.
+
+*/
+#include <stdio.h>
+
+/*  ENCODE  -- Encode binary file into base64.  */
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "base64.h"
+
+static unsigned char dtable[512];
+
+void buffer_new(struct buffer_st *b)
+{
+  b->length = 512;
+  b->data = malloc(sizeof(char)*(b->length));
+  b->data[0] = 0;
+  b->ptr = b->data;
+  b->offset = 0;
+}
+
+void buffer_add(struct buffer_st *b, char c)
+{
+  *(b->ptr++) = c;
+  b->offset++;
+  if (b->offset == b->length) {
+    b->length += 512;
+    b->data = realloc(b->data, b->length);
+    b->ptr = b->data + b->offset;
+  }
+}
+
+void buffer_delete(struct buffer_st *b)
+{
+  free(b->data);
+  b->length = 0;
+  b->offset = 0;
+  b->ptr = NULL;
+  b->data = NULL;
+}
+
+void base64_encode(struct buffer_st *b, const char *source, int length)
+{
+  int i, hiteof = 0;
+  int offset = 0;
+  int olen;
+  
+  olen = 0;
+  
+  buffer_new(b);
+  
+  /*   Fill dtable with character encodings.  */
+  
+  for (i = 0; i < 26; i++) {
+    dtable[i] = 'A' + i;
+    dtable[26 + i] = 'a' + i;
+  }
+  for (i = 0; i < 10; i++) {
+    dtable[52 + i] = '0' + i;
+  }
+  dtable[62] = '+';
+  dtable[63] = '/';
+  
+  while (!hiteof) {
+    unsigned char igroup[3], ogroup[4];
+    int c, n;
+    
+    igroup[0] = igroup[1] = igroup[2] = 0;
+    for (n = 0; n < 3; n++) {
+      c = *(source++);
+      offset++;
+      if (offset > length) {
+       hiteof = 1;
+       break;
+      }
+      igroup[n] = (unsigned char) c;
+    }
+    if (n > 0) {
+      ogroup[0] = dtable[igroup[0] >> 2];
+      ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
+      ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
+      ogroup[3] = dtable[igroup[2] & 0x3F];
+      
+      /* Replace characters in output stream with "=" pad
+        characters if fewer than three characters were
+        read from the end of the input stream. */
+      
+      if (n < 3) {
+       ogroup[3] = '=';
+       if (n < 2) {
+         ogroup[2] = '=';
+       }
+      }
+      for (i = 0; i < 4; i++) {
+       buffer_add(b, ogroup[i]);
+       if (!(b->offset % 72)) {
+         /* buffer_add(b, '\r'); */
+         buffer_add(b, '\n');
+       }
+      }
+    }
+  }
+  /* buffer_add(b, '\r'); */
+  buffer_add(b, '\n');
+}
+
+void base64_decode(struct buffer_st *bfr, const char *source, int length)
+{
+    int i;
+    int offset = 0;
+    int endoffile;
+    int count;
+
+    buffer_new(bfr);
+
+    for (i = 0; i < 255; i++) {
+       dtable[i] = 0x80;
+    }
+    for (i = 'A'; i <= 'Z'; i++) {
+        dtable[i] = 0 + (i - 'A');
+    }
+    for (i = 'a'; i <= 'z'; i++) {
+        dtable[i] = 26 + (i - 'a');
+    }
+    for (i = '0'; i <= '9'; i++) {
+        dtable[i] = 52 + (i - '0');
+    }
+    dtable['+'] = 62;
+    dtable['/'] = 63;
+    dtable['='] = 0;
+
+    endoffile = 0;
+
+    /*CONSTANTCONDITION*/
+    while (1) {
+       unsigned char a[4], b[4], o[3];
+
+       for (i = 0; i < 4; i++) {
+           int c;
+           while (1) {
+             c = *(source++);
+             offset++;
+             if (offset > length) endoffile = 1;
+             if (isspace(c) || c == '\n' || c == '\r') continue;
+             break;
+           }
+
+           if (endoffile) {
+             /*
+               if (i > 0) {
+                    fprintf(stderr, "Input file incomplete.\n");
+                   exit(1);
+               }
+             */
+               return;
+           }
+
+           if (dtable[c] & 0x80) {
+             /*
+             fprintf(stderr, "Offset %i length %i\n", offset, length);
+             fprintf(stderr, "character '%c:%x:%c' in input file.\n", c, c, dtable[c]);
+             exit(1);
+             */
+             i--;
+             continue;
+           }
+           a[i] = (unsigned char) c;
+           b[i] = (unsigned char) dtable[c];
+       }
+       o[0] = (b[0] << 2) | (b[1] >> 4);
+       o[1] = (b[1] << 4) | (b[2] >> 2);
+       o[2] = (b[2] << 6) | b[3];
+        i = a[2] == '=' ? 1 : (a[3] == '=' ? 2 : 3);
+       count = 0;
+       while (count < i) {
+         buffer_add(bfr, o[count++]);
+       }
+       if (i < 3) {
+           return;
+       }
+    }
+}
diff --git a/php/xmlrpc/libxmlrpc/base64.h b/php/xmlrpc/libxmlrpc/base64.h
new file mode 100644 (file)
index 0000000..4cf156a
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+
+          Encode or decode file as MIME base64 (RFC 1341)
+
+                           by John Walker
+                      http://www.fourmilab.ch/
+
+               This program is in the public domain.
+
+*/
+
+
+struct buffer_st {
+  char *data;
+  int length;
+  char *ptr;
+  int offset;
+};
+
+void buffer_new(struct buffer_st *b);
+void buffer_add(struct buffer_st *b, char c);
+void buffer_delete(struct buffer_st *b);
+
+void base64_encode(struct buffer_st *b, const char *source, int length);
+void base64_decode(struct buffer_st *b, const char *source, int length);
+
+/*
+#define DEBUG_MALLOC
+ */
+
+#ifdef DEBUG_MALLOC
+void *_malloc_real(size_t s, char *file, int line);
+void _free_real(void *p, char *file, int line);
+
+#define malloc(s)      _malloc_real(s,__FILE__,__LINE__)
+#define free(p)                _free_real(p, __FILE__,__LINE__)
+#endif
+
diff --git a/php/xmlrpc/libxmlrpc/encodings.c b/php/xmlrpc/libxmlrpc/encodings.c
new file mode 100644 (file)
index 0000000..b7f7fcb
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef PHP_WIN32
+#include <php_config.h>
+#else
+#include <config.w32.h>
+#include <stdlib.h>
+#endif
+
+static const char rcsid[] = "#(@) $Id: encodings.c 5574 2007-10-25 20:33:17Z thierry $";
+
+#include <errno.h>
+
+#ifdef HAVE_GICONV_H
+#include <giconv.h>
+#else
+#include <iconv.h>
+#endif
+
+#include "encodings.h"
+
+static char* convert(const char* src, int src_len, int *new_len, const char* from_enc, const char* to_enc) {
+   char* outbuf = 0;
+
+   if(src && src_len && from_enc && to_enc) {
+      size_t outlenleft = src_len;
+      size_t inlenleft = src_len;
+      int outlen = src_len;
+      iconv_t ic = iconv_open(to_enc, from_enc);
+      char* out_ptr = 0;
+
+      if(ic != (iconv_t)-1) {
+         size_t st;
+         outbuf = (char*)malloc(outlen + 1);
+
+         if(outbuf) {
+            out_ptr = (char*)outbuf;
+            while(inlenleft) {
+               st = iconv(ic, (char**)&src, &inlenleft, &out_ptr, &outlenleft);
+               if(st == -1) {
+                  if(errno == E2BIG) {
+                     int diff = out_ptr - outbuf;
+                     outlen += inlenleft;
+                     outlenleft += inlenleft;
+                     outbuf = (char*)realloc(outbuf, outlen + 1);
+                     if(!outbuf) {
+                        break;
+                     }
+                     out_ptr = outbuf + diff;
+                  }
+                  else {
+                     free(outbuf);
+                     outbuf = 0;
+                     break;
+                  }
+               }
+            }
+         }
+         iconv_close(ic);
+      }
+      outlen -= outlenleft;
+
+      if(new_len) {
+         *new_len = outbuf ? outlen : 0;
+      }
+      if(outbuf) {
+         outbuf[outlen] = 0;
+      }
+   }
+   return outbuf;
+}
+
+/* returns a new string that must be freed */
+char* utf8_encode(const char *s, int len, int *newlen, const char* encoding)
+{
+   return convert(s, len, newlen, encoding, "UTF-8");
+}
+
+/* returns a new string, possibly decoded */
+char* utf8_decode(const char *s, int len, int *newlen, const char* encoding)
+{
+   return convert(s, len, newlen, "UTF-8", encoding);
+}
+
diff --git a/php/xmlrpc/libxmlrpc/encodings.h b/php/xmlrpc/libxmlrpc/encodings.h
new file mode 100644 (file)
index 0000000..486360b
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+#ifndef __ENCODINGS__H
+#define __ENCODINGS__H
+
+/* these defines are for legacy purposes. */
+#define encoding_utf_8 "UTF-8"
+typedef const char* ENCODING_ID;
+#define utf8_get_encoding_id_string(desired_enc) ((const char*)desired_enc)
+#define utf8_get_encoding_id_from_string(id_string) ((ENCODING_ID)id_string)
+
+char* utf8_encode(const char *s, int len, int *newlen, ENCODING_ID encoding);
+char* utf8_decode(const char *s, int len, int *newlen, ENCODING_ID encoding);
+
+#endif /* __ENCODINGS__H  */
diff --git a/php/xmlrpc/libxmlrpc/queue.c b/php/xmlrpc/libxmlrpc/queue.c
new file mode 100644 (file)
index 0000000..1bbf5a3
--- /dev/null
@@ -0,0 +1,982 @@
+static const char rcsid[] = "#(@) $Id: queue.c 5574 2007-10-25 20:33:17Z thierry $";
+
+/* 
+ * Date last modified: Jan 2001
+ * Modifications by Dan Libby (dan@libby.com), including:
+ *  - various fixes, null checks, etc
+ *  - addition of Q_Iter funcs, macros
+ */
+
+
+/*-**************************************************************
+ *
+ *  File : q.c
+ *
+ *  Author: Peter Yard [1993.01.02] -- 02 Jan 1993
+ *
+ *  Disclaimer: This code is released to the public domain.
+ *
+ *  Description:
+ *      Generic double ended queue (Deque pronounced DEK) for handling
+ *      any data types, with sorting.
+ *
+ *      By use of various functions in this module the caller
+ *      can create stacks, queues, lists, doubly linked lists,
+ *      sorted lists, indexed lists.  All lists are dynamic.
+ *
+ *      It is the responsibility of the caller to malloc and free
+ *      memory for insertion into the queue. A pointer to the object
+ *      is used so that not only can any data be used but various kinds
+ *      of data can be pushed on the same queue if one so wished e.g.
+ *      various length string literals mixed with pointers to structures
+ *      or integers etc.
+ *
+ *  Enhancements:
+ *      A future improvement would be the option of multiple "cursors"
+ *      so that multiple locations could occur in the one queue to allow
+ *      placemarkers and additional flexibility.  Perhaps even use queue
+ *      itself to have a list of cursors.
+ *
+ * Usage:
+ *
+ *          /x init queue x/
+ *          queue  q;
+ *          Q_Init(&q);
+ *
+ *      To create a stack :
+ *
+ *          Q_PushHead(&q, &mydata1); /x push x/
+ *          Q_PushHead(&q, &mydata2);
+ *          .....
+ *          data_ptr = Q_PopHead(&q); /x pop x/
+ *          .....
+ *          data_ptr = Q_Head(&q);   /x top of stack x/
+ *
+ *      To create a FIFO:
+ *
+ *          Q_PushHead(&q, &mydata1);
+ *          .....
+ *          data_ptr = Q_PopTail(&q);
+ *
+ *      To create a double list:
+ *
+ *          data_ptr = Q_Head(&q);
+ *          ....
+ *          data_ptr = Q_Next(&q);
+ *          data_ptr = Q_Tail(&q);
+ *          if (Q_IsEmpty(&q)) ....
+ *          .....
+ *          data_ptr = Q_Previous(&q);
+ *
+ *      To create a sorted list:
+ *
+ *          Q_PushHead(&q, &mydata1); /x push x/
+ *          Q_PushHead(&q, &mydata2);
+ *          .....
+ *          if (!Q_Sort(&q, MyFunction))
+ *              .. error ..
+ *
+ *          /x fill in key field of mydata1.
+ *           * NB: Q_Find does linear search
+ *           x/
+ *
+ *          if (Q_Find(&q, &mydata1, MyFunction))
+ *          {
+ *              /x found it, queue cursor now at correct record x/
+ *              /x can retrieve with x/
+ *              data_ptr = Q_Get(&q);
+ *
+ *              /x alter data , write back with x/
+ *              Q_Put(&q, data_ptr);
+ *          }
+ *
+ *          /x Search with binary search x/
+ *          if (Q_Seek(&q, &mydata, MyFunction))
+ *              /x etc x/
+ *
+ *
+ ****************************************************************/
+
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include <stdlib.h>
+#include "queue.h"
+
+
+static void QuickSort(void *list[], int low, int high,
+                      int (*Comp)(const void *, const void *));
+static int  Q_BSearch(queue *q, void *key,
+                      int (*Comp)(const void *, const void *));
+
+/* The index: a pointer to pointers */
+
+static  void        **index;
+static  datanode    **posn_index;
+
+
+/***
+ *
+ ** function    : Q_Init
+ *
+ ** purpose     : Initialise queue object and pointers.
+ *
+ ** parameters  : 'queue' pointer.
+ *
+ ** returns     : True_ if init successful else False_
+ *
+ ** comments    :
+ ***/
+
+int Q_Init(queue  *q)
+{
+   if(q) {
+      q->head = q->tail = NULL;
+      q->cursor = q->head;
+      q->size = 0;
+      q->sorted = False_;
+   }
+
+   return True_;
+}
+
+/***
+ *
+ ** function    : Q_AtHead
+ *
+ ** purpose     : tests if cursor is at head of queue
+ *
+ ** parameters  : 'queue' pointer.
+ *
+ ** returns     : boolean - True_ is at head else False_
+ *
+ ** comments    :
+ *
+ ***/
+
+int Q_AtHead(queue *q)
+{
+   return(q && q->cursor == q->head);
+}
+
+
+/***
+ *
+ ** function    : Q_AtTail
+ *
+ ** purpose     : boolean test if cursor at tail of queue
+ *
+ ** parameters  : 'queue' pointer to test.
+ *
+ ** returns     : True_ or False_
+ *
+ ** comments    :
+ *
+ ***/
+
+int Q_AtTail(queue *q)
+{
+   return(q && q->cursor == q->tail);
+}
+
+
+/***
+ *
+ ** function    : Q_IsEmpty
+ *
+ ** purpose     : test if queue has nothing in it.
+ *
+ ** parameters  : 'queue' pointer
+ *
+ ** returns     : True_ if IsEmpty queue, else False_
+ *
+ ** comments    :
+ *
+ ***/
+
+inline int Q_IsEmpty(queue *q)
+{
+   return(!q || q->size == 0);
+}
+
+/***
+ *
+ ** function    : Q_Size
+ *
+ ** purpose     : return the number of elements in the queue
+ *
+ ** parameters  : queue pointer
+ *
+ ** returns     : number of elements
+ *
+ ** comments    :
+ *
+ ***/
+
+int Q_Size(queue *q)
+{
+   return q ? q->size : 0;
+}
+
+
+/***
+ *
+ ** function    : Q_Head
+ *
+ ** purpose     : position queue cursor to first element (head) of queue.
+ *
+ ** parameters  : 'queue' pointer
+ *
+ ** returns     : pointer to data at head. If queue is IsEmpty returns NULL
+ *
+ ** comments    :
+ *
+ ***/
+
+void *Q_Head(queue *q)
+{
+   if(Q_IsEmpty(q))
+      return NULL;
+
+   q->cursor = q->head;
+
+   return  q->cursor->data;
+}
+
+
+/***
+ *
+ ** function    : Q_Tail
+ *
+ ** purpose     : locate cursor at tail of queue.
+ *
+ ** parameters  : 'queue' pointer
+ *
+ ** returns     : pointer to data at tail , if queue IsEmpty returns NULL
+ *
+ ** comments    :
+ *
+ ***/
+
+void *Q_Tail(queue *q)
+{
+   if(Q_IsEmpty(q))
+      return NULL;
+
+   q->cursor = q->tail;
+
+   return  q->cursor->data;
+}
+
+
+/***
+ *
+ ** function    : Q_PushHead
+ *
+ ** purpose     : put a data pointer at the head of the queue
+ *
+ ** parameters  : 'queue' pointer, void pointer to the data.
+ *
+ ** returns     : True_ if success else False_ if unable to push data.
+ *
+ ** comments    :
+ *
+ ***/
+
+int Q_PushHead(queue *q, void *d)
+{
+   if(q && d) {
+      node    *n;
+      datanode *p;
+
+      p = malloc(sizeof(datanode));
+      if(p == NULL)
+         return False_;
+
+      n = q->head;
+
+      q->head = (node*)p;
+      q->head->prev = NULL;
+
+      if(q->size == 0) {
+         q->head->next = NULL;
+         q->tail = q->head;
+      }
+      else {
+         q->head->next = (datanode*)n;
+         n->prev = q->head;
+      }
+
+      q->head->data = d;
+      q->size++;
+
+      q->cursor = q->head;
+
+      q->sorted = False_;
+
+      return True_;
+   }
+   return False_;
+}
+
+
+
+/***
+ *
+ ** function    : Q_PushTail
+ *
+ ** purpose     : put a data element pointer at the tail of the queue
+ *
+ ** parameters  : queue pointer, pointer to the data
+ *
+ ** returns     : True_ if data pushed, False_ if data not inserted.
+ *
+ ** comments    :
+ *
+ ***/
+
+int Q_PushTail(queue *q, void *d)
+{
+   if(q && d) {
+      node        *p;
+      datanode    *n;
+
+      n = malloc(sizeof(datanode));
+      if(n == NULL)
+         return False_;
+
+      p = q->tail;
+      q->tail = (node *)n;
+
+      if(q->size == 0) {
+         q->tail->prev = NULL;
+         q->head = q->tail;
+      }
+      else {
+         q->tail->prev = (datanode *)p;
+         p->next = q->tail;
+      }
+
+      q->tail->next = NULL;
+
+      q->tail->data =  d;
+      q->cursor = q->tail;
+      q->size++;
+
+      q->sorted = False_;
+
+      return True_;
+   }
+   return False_;
+}
+
+
+
+/***
+ *
+ ** function    : Q_PopHead
+ *
+ ** purpose     : remove and return the top element at the head of the
+ *                queue.
+ *
+ ** parameters  : queue pointer
+ *
+ ** returns     : pointer to data element or NULL if queue is IsEmpty.
+ *
+ ** comments    :
+ *
+ ***/
+
+void *Q_PopHead(queue *q)
+{
+   datanode    *n;
+   void        *d;
+
+   if(Q_IsEmpty(q))
+      return NULL;
+
+   d = q->head->data;
+   n = q->head->next;
+   free(q->head);
+
+   q->size--;
+
+   if(q->size == 0)
+      q->head = q->tail = q->cursor = NULL;
+   else {
+      q->head = (node *)n;
+      q->head->prev = NULL;
+      q->cursor = q->head;
+   }
+
+   q->sorted = False_;
+
+   return d;
+}
+
+
+/***
+ *
+ ** function    : Q_PopTail
+ *
+ ** purpose     : remove element from tail of queue and return data.
+ *
+ ** parameters  : queue pointer
+ *
+ ** returns     : pointer to data element that was at tail. NULL if queue
+ *                IsEmpty.
+ *
+ ** comments    :
+ *
+ ***/
+
+void *Q_PopTail(queue *q)
+{
+   datanode    *p;
+   void        *d;
+
+   if(Q_IsEmpty(q))
+      return NULL;
+
+   d = q->tail->data;
+   p = q->tail->prev;
+   free(q->tail);
+   q->size--;
+
+   if(q->size == 0)
+      q->head = q->tail = q->cursor = NULL;
+   else {
+      q->tail = (node *)p;
+      q->tail->next = NULL;
+      q->cursor = q->tail;
+   }
+
+   q->sorted = False_;
+
+   return d;
+}
+
+
+
+/***
+ *
+ ** function    : Q_Next
+ *
+ ** purpose     : Move to the next element in the queue without popping
+ *
+ ** parameters  : queue pointer.
+ *
+ ** returns     : pointer to data element of new element or NULL if end
+ *                of the queue.
+ *
+ ** comments    : This uses the cursor for the current position. Q_Next
+ *                only moves in the direction from the head of the queue
+ *                to the tail.
+ ***/
+
+void *Q_Next(queue *q)
+{
+   if(!q)
+      return NULL;
+
+   if(!q->cursor || q->cursor->next == NULL)
+      return NULL;
+
+   q->cursor = (node *)q->cursor->next;
+
+   return  q->cursor->data ;
+}
+
+
+
+/***
+ *
+ ** function    : Q_Previous
+ *
+ ** purpose     : Opposite of Q_Next. Move to next element closer to the
+ *                head of the queue.
+ *
+ ** parameters  : pointer to queue
+ *
+ ** returns     : pointer to data of new element else NULL if queue IsEmpty
+ *
+ ** comments    : Makes cursor move towards the head of the queue.
+ *
+ ***/
+
+void *Q_Previous(queue *q)
+{
+   if(!q)
+      return NULL;
+   
+   if(q->cursor->prev == NULL)
+      return NULL;
+
+   q->cursor = (node *)q->cursor->prev;
+
+   return q->cursor->data;
+}
+
+
+void *Q_Iter_Del(queue *q, q_iter iter)
+{
+   void        *d;
+   datanode    *n, *p;
+
+   if(!q)
+      return NULL;
+
+   if(iter == NULL)
+      return NULL;
+
+   if(iter == (q_iter)q->head)
+      return Q_PopHead(q);
+
+   if(iter == (q_iter)q->tail)
+      return Q_PopTail(q);
+
+   n = ((node*)iter)->next;
+   p = ((node*)iter)->prev;
+   d = ((node*)iter)->data;
+
+   free(iter);
+
+   if(p) {
+      p->next = n;
+   }
+   if (q->cursor == (node*)iter) {
+      if (p) {
+         q->cursor = p;
+      } else {
+         q->cursor = n;
+      }
+   }
+
+
+   if (n != NULL) {
+       n->prev = p;
+   }
+
+   q->size--;
+
+   q->sorted = False_;
+
+   return d;
+}
+
+
+
+/***
+ *
+ ** function    : Q_DelCur
+ *
+ ** purpose     : Delete the current queue element as pointed to by
+ *                the cursor.
+ *
+ ** parameters  : queue pointer
+ *
+ ** returns     : pointer to data element.
+ *
+ ** comments    : WARNING! It is the responsibility of the caller to
+ *                free any memory. Queue cannot distinguish between
+ *                pointers to literals and malloced memory.
+ *
+ ***/
+
+void *Q_DelCur(queue* q) {
+   if(q) {
+      return Q_Iter_Del(q, (q_iter)q->cursor);
+   }
+   return 0;
+}
+
+
+/***
+ *
+ ** function    : Q_Destroy
+ *
+ ** purpose     : Free all queue resources
+ *
+ ** parameters  : queue pointer
+ *
+ ** returns     : null.
+ *
+ ** comments    : WARNING! It is the responsibility of the caller to
+ *                free any memory. Queue cannot distinguish between
+ *                pointers to literals and malloced memory.
+ *
+ ***/
+
+void Q_Destroy(queue *q)
+{
+   while(!Q_IsEmpty(q)) {
+      Q_PopHead(q);
+   }
+}
+
+
+/***
+ *
+ ** function    : Q_Get
+ *
+ ** purpose     : get the pointer to the data at the cursor location
+ *
+ ** parameters  : queue pointer
+ *
+ ** returns     : data element pointer
+ *
+ ** comments    :
+ *
+ ***/
+
+void *Q_Get(queue *q)
+{
+   if(!q)
+      return NULL;
+
+   if(q->cursor == NULL)
+      return NULL;
+   return q->cursor->data;
+}
+
+
+
+/***
+ *
+ ** function    : Q_Put
+ *
+ ** purpose     : replace pointer to data with new pointer to data.
+ *
+ ** parameters  : queue pointer, data pointer
+ *
+ ** returns     : boolean- True_ if successful, False_ if cursor at NULL
+ *
+ ** comments    :
+ *
+ ***/
+
+int Q_Put(queue *q, void *data)
+{
+   if(q && data) {
+      if(q->cursor == NULL)
+         return False_;
+
+      q->cursor->data = data;
+      return True_;
+   }
+   return False_;
+}
+
+
+/***
+ *
+ ** function    : Q_Find
+ *
+ ** purpose     : Linear search of queue for match with key in *data
+ *
+ ** parameters  : queue pointer q, data pointer with data containing key
+ *                comparison function here called Comp.
+ *
+ ** returns     : True_ if found , False_ if not in queue.
+ *
+ ** comments    : Useful for small queues that are constantly changing
+ *                and would otherwise need constant sorting with the
+ *                Q_Seek function.
+ *                For description of Comp see Q_Sort.
+ *                Queue cursor left on position found item else at end.
+ *
+ ***/
+
+int Q_Find(queue *q, void *data,
+           int (*Comp)(const void *, const void *))
+{
+   void *d;
+
+   if (q == NULL) {
+       return False_;
+   }
+
+   d = Q_Head(q);
+   do {
+      if(Comp(d, data) == 0)
+         return True_;
+      d = Q_Next(q);
+   } while(!Q_AtTail(q));
+
+   if(Comp(d, data) == 0)
+      return True_;
+
+   return False_;
+}
+
+/*========  Sorted Queue and Index functions   ========= */
+
+
+static void QuickSort(void *list[], int low, int high,
+                      int (*Comp)(const void *, const void *))
+{
+   int     flag = 1, i, j;
+   void    *key, *temp;
+
+   if(low < high) {
+      i = low;
+      j = high + 1;
+
+      key = list[ low ];
+
+      while(flag) {
+         i++;
+         while(Comp(list[i], key) < 0)
+            i++;
+
+         j--;
+         while(Comp(list[j], key) > 0)
+            j--;
+
+         if(i < j) {
+            temp = list[i];
+            list[i] = list[j];
+            list[j] = temp;
+         }
+         else  flag = 0;
+      }
+
+      temp = list[low];
+      list[low] = list[j];
+      list[j] = temp;
+
+      QuickSort(list, low, j-1, Comp);
+      QuickSort(list, j+1, high, Comp);
+   }
+}
+
+
+/***
+ *
+ ** function    : Q_Sort
+ *
+ ** purpose     : sort the queue and allow index style access.
+ *
+ ** parameters  : queue pointer, comparison function compatible with
+ *                with 'qsort'.
+ *
+ ** returns     : True_ if sort succeeded. False_ if error occurred.
+ *
+ ** comments    : Comp function supplied by caller must return
+ *                  -1 if data1  < data2
+ *                   0 if data1 == data2
+ *                  +1 if data1  > data2
+ *
+ *                    for Comp(data1, data2)
+ *
+ *                If queue is already sorted it frees the memory of the
+ *                old index and starts again.
+ *
+ ***/
+
+int Q_Sort(queue *q, int (*Comp)(const void *, const void *))
+{
+   int         i;
+   void        *d;
+   datanode    *dn;
+
+   /* if already sorted free memory for tag array */
+
+   if(q->sorted) {
+      free(index);
+      free(posn_index);
+      q->sorted = False_;
+   }
+
+   /* Now allocate memory of array, array of pointers */
+
+   index = malloc(q->size * sizeof(q->cursor->data));
+   if(index == NULL)
+      return False_;
+
+   posn_index = malloc(q->size * sizeof(q->cursor));
+   if(posn_index == NULL) {
+      free(index);
+      return False_;
+   }
+
+   /* Walk queue putting pointers into array */
+
+   d = Q_Head(q);
+   for(i=0; i < q->size; i++) {
+      index[i] = d;
+      posn_index[i] = q->cursor;
+      d = Q_Next(q);
+   }
+
+   /* Now sort the index */
+
+   QuickSort(index, 0, q->size - 1, Comp);
+
+   /* Rearrange the actual queue into correct order */
+
+   dn = q->head;
+   i = 0;
+   while(dn != NULL) {
+      dn->data = index[i++];
+      dn = dn->next;
+   }
+
+   /* Re-position to original element */
+
+   if(d != NULL)
+      Q_Find(q, d, Comp);
+   else  Q_Head(q);
+
+   q->sorted = True_;
+
+   return True_;
+}
+
+
+/***
+ *
+ ** function    : Q_BSearch
+ *
+ ** purpose     : binary search of queue index for node containing key
+ *
+ ** parameters  : queue pointer 'q', data pointer of key 'key',
+ *                  Comp comparison function.
+ *
+ ** returns     : integer index into array of node pointers,
+ *                or -1 if not found.
+ *
+ ** comments    : see Q_Sort for description of 'Comp' function.
+ *
+ ***/
+
+static int Q_BSearch( queue *q, void *key,
+                      int (*Comp)(const void *, const void*))
+{
+   int low, mid, hi, val;
+
+   low = 0;
+   hi = q->size - 1;
+
+   while(low <= hi) {
+      mid = (low + hi) / 2;
+      val = Comp(key, index[ mid ]);
+
+      if(val < 0)
+         hi = mid - 1;
+
+      else if(val > 0)
+         low = mid + 1;
+
+      else /* Success */
+         return mid;
+   }
+
+   /* Not Found */
+
+   return -1;
+}
+
+
+/***
+ *
+ ** function    : Q_Seek
+ *
+ ** purpose     : use index to locate data according to key in 'data'
+ *
+ ** parameters  : queue pointer 'q', data pointer 'data', Comp comparison
+ *                function.
+ *
+ ** returns     : pointer to data or NULL if could not find it or could
+ *                not sort queue.
+ *
+ ** comments    : see Q_Sort for description of 'Comp' function.
+ *
+ ***/
+
+void *Q_Seek(queue *q, void *data, int (*Comp)(const void *, const void *))
+{
+   int idx;
+
+   if (q == NULL) {
+       return NULL;
+   }
+
+   if(!q->sorted) {
+      if(!Q_Sort(q, Comp))
+         return NULL;
+   }
+
+   idx = Q_BSearch(q, data, Comp);
+
+   if(idx < 0)
+      return NULL;
+
+   q->cursor = posn_index[idx];
+
+   return index[idx];
+}
+
+
+
+/***
+ *
+ ** function    : Q_Insert
+ *
+ ** purpose     : Insert an element into an indexed queue
+ *
+ ** parameters  : queue pointer 'q', data pointer 'data', Comp comparison
+ *                function.
+ *
+ ** returns     : pointer to data or NULL if could not find it or could
+ *                not sort queue.
+ *
+ ** comments    : see Q_Sort for description of 'Comp' function.
+ *                WARNING! This code can be very slow since each new
+ *                element means a new Q_Sort.  Should only be used for
+ *                the insertion of the odd element ,not the piecemeal
+ *                building of an entire queue.
+ ***/
+
+int Q_Insert(queue *q, void *data, int (*Comp)(const void *, const void *))
+{
+   if (q == NULL) {
+       return False_;
+   }
+
+   Q_PushHead(q, data);
+
+   if(!Q_Sort(q, Comp))
+      return False_;
+
+   return True_;
+}
+
+/* read only funcs for iterating through queue. above funcs modify queue */
+q_iter Q_Iter_Head(queue *q) {
+   return q ? (q_iter)q->head : NULL;
+}
+
+q_iter Q_Iter_Tail(queue *q) {
+   return q ? (q_iter)q->tail : NULL;
+}
+
+q_iter Q_Iter_Next(q_iter qi) {
+   return qi ? (q_iter)((node*)qi)->next : NULL;
+}
+
+q_iter Q_Iter_Prev(q_iter qi) {
+   return qi ? (q_iter)((node*)qi)->prev : NULL;
+}
+
+void * Q_Iter_Get(q_iter qi) {
+   return qi ? ((node*)qi)->data : NULL;
+}
+
+int Q_Iter_Put(q_iter qi, void* data) {
+   if(qi) {
+      ((node*)qi)->data = data;
+      return True_;
+   }
+   return False_;
+}
diff --git a/php/xmlrpc/libxmlrpc/queue.h b/php/xmlrpc/libxmlrpc/queue.h
new file mode 100644 (file)
index 0000000..be73f6d
--- /dev/null
@@ -0,0 +1,89 @@
+/* 
+ * Date last modified: Jan 2001
+ * Modifications by Dan Libby (dan@libby.com), including:
+ *  - various fixes, null checks, etc
+ *  - addition of Q_Iter funcs, macros
+ */
+
+/*
+ *  File : q.h
+ *
+ *  Peter Yard  02 Jan 1993.
+ *
+ *  Disclaimer: This code is released to the public domain.
+ */
+
+#ifndef Q__H
+#define Q__H
+
+#ifndef False_
+   #define False_ 0
+#endif
+
+#ifndef True_
+   #define True_ 1
+#endif
+
+typedef struct nodeptr datanode;
+
+typedef struct nodeptr {
+   void        *data ;
+   datanode    *prev, *next ;
+} node ;
+
+/* For external use with Q_Iter* funcs */
+typedef struct nodeptr* q_iter;
+
+typedef struct {
+   node        *head, *tail, *cursor;
+   int         size, sorted, item_deleted;
+} queue;
+
+typedef  struct {
+   void        *dataptr;
+   node        *loc ;
+} index_elt ;
+
+
+int    Q_Init(queue  *q);
+void   Q_Destroy(queue *q);
+int    Q_IsEmpty(queue *q);
+int    Q_Size(queue *q);
+int    Q_AtHead(queue *q);
+int    Q_AtTail(queue *q);
+int    Q_PushHead(queue *q, void *d);
+int    Q_PushTail(queue *q, void *d);
+void  *Q_Head(queue *q);
+void  *Q_Tail(queue *q);
+void  *Q_PopHead(queue *q);
+void  *Q_PopTail(queue *q);
+void  *Q_Next(queue *q);
+void  *Q_Previous(queue *q);
+void  *Q_DelCur(queue *q);
+void  *Q_Get(queue *q);
+int    Q_Put(queue *q, void *data);
+int    Q_Sort(queue *q, int (*Comp)(const void *, const void *));
+int    Q_Find(queue *q, void *data,
+              int (*Comp)(const void *, const void *));
+void  *Q_Seek(queue *q, void *data,
+              int (*Comp)(const void *, const void *));
+int    Q_Insert(queue *q, void *data,
+                int (*Comp)(const void *, const void *));
+
+/* read only funcs for iterating through queue. above funcs modify queue */
+q_iter Q_Iter_Head(queue *q);
+q_iter Q_Iter_Tail(queue *q);
+q_iter Q_Iter_Next(q_iter qi);
+q_iter Q_Iter_Prev(q_iter qi);
+void*  Q_Iter_Get(q_iter qi);
+int    Q_Iter_Put(q_iter qi, void* data); /* not read only! here for completeness. */
+void*  Q_Iter_Del(queue *q, q_iter iter); /* not read only! here for completeness. */
+
+/* Fast (macro'd) versions of above */
+#define Q_Iter_Head_F(q) (q ? (q_iter)((queue*)q)->head : NULL)
+#define Q_Iter_Tail_F(q) (q ? (q_iter)((queue*)q)->tail : NULL)
+#define Q_Iter_Next_F(qi) (qi ? (q_iter)((node*)qi)->next : NULL)
+#define Q_Iter_Prev_F(qi) (qi ? (q_iter)((node*)qi)->prev : NULL)
+#define Q_Iter_Get_F(qi) (qi ? ((node*)qi)->data : NULL)
+
+#endif /* Q__H */
diff --git a/php/xmlrpc/libxmlrpc/simplestring.c b/php/xmlrpc/libxmlrpc/simplestring.c
new file mode 100644 (file)
index 0000000..a7dd727
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+static const char rcsid[] = "#(@) $Id: simplestring.c 5574 2007-10-25 20:33:17Z thierry $";
+
+
+#define SIMPLESTRING_INCR 32
+
+/****h* ABOUT/simplestring
+ * NAME
+ *   simplestring
+ * AUTHOR
+ *   Dan Libby, aka danda  (dan@libby.com)
+ * CREATION DATE
+ *   06/2000
+ * HISTORY
+ *   $Log: simplestring.c,v $
+ *   Revision 1.4  2003/12/16 21:00:21  sniper
+ *   Fix some compile warnings (patch by Joe Orton)
+ *
+ *   Revision 1.3  2002/08/22 01:25:50  sniper
+ *   kill some compile warnings
+ *
+ *   Revision 1.2  2002/07/05 04:43:53  danda
+ *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
+ *
+ *   Revision 1.4  2002/02/13 20:58:50  danda
+ *   patch to make source more windows friendly, contributed by Jeff Lawson
+ *
+ *   Revision 1.3  2001/09/29 21:58:05  danda
+ *   adding cvs log to history section
+ *
+ *   10/15/2000 -- danda -- adding robodoc documentation
+ * PORTABILITY
+ *   Coded on RedHat Linux 6.2.  Builds on Solaris x86.  Should build on just
+ *   about anything with minor mods.
+ * NOTES
+ *   This code was written primarily for xmlrpc, but has found some other uses.
+ *
+ *   simplestring is, as the name implies, a simple API for dealing with C strings.
+ *   Why would I write yet another string API?  Because I couldn't find any that were
+ *   a) free / GPL, b) simple/lightweight, c) fast, not doing unneccesary strlens all
+ *   over the place.  So.  It is simple, and it seems to work, and it is pretty fast.
+ *
+ *   Oh, and it is also binary safe, ie it can handle strings with embedded NULLs,
+ *   so long as the real length is passed in.
+ *   
+ *   And the masses rejoiced.
+ *
+ * BUGS
+ *   there must be some.
+ ******/
+
+#include <stdlib.h>
+#include <string.h>
+#include "simplestring.h"
+
+#define my_free(thing)  if(thing) {free(thing); thing = 0;}
+
+/*----------------------**
+* Begin String Functions *
+*-----------------------*/
+
+/****f* FUNC/simplestring_init
+ * NAME
+ *   simplestring_init
+ * SYNOPSIS
+ *   void simplestring_init(simplestring* string)
+ * FUNCTION
+ *   initialize string
+ * INPUTS
+ *   string - pointer to a simplestring struct that will be initialized
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   simplestring_free ()
+ *   simplestring_clear ()
+ * SOURCE
+ */
+void simplestring_init(simplestring* string) {
+   memset(string, 0, sizeof(simplestring));
+}
+/******/
+
+static void simplestring_init_str(simplestring* string) {
+   string->str = (char*)malloc(SIMPLESTRING_INCR);
+   if(string->str) {
+      string->str[0] = 0;
+      string->len = 0;
+      string->size = SIMPLESTRING_INCR;
+   }
+   else {
+      string->size = 0;
+   }
+}
+
+/****f* FUNC/simplestring_clear
+ * NAME
+ *   simplestring_clear
+ * SYNOPSIS
+ *   void simplestring_clear(simplestring* string)
+ * FUNCTION
+ *   clears contents of a string
+ * INPUTS
+ *   string - the string value to clear
+ * RESULT
+ *   void
+ * NOTES
+ *   This function is very fast as it does not de-allocate any memory.
+ * SEE ALSO
+ * 
+ * SOURCE
+ */
+void simplestring_clear(simplestring* string) {
+   if(string->str) {
+      string->str[0] = 0;
+   }
+   string->len = 0;
+}
+/******/
+
+/****f* FUNC/simplestring_free
+ * NAME
+ *   simplestring_free
+ * SYNOPSIS
+ *   void simplestring_free(simplestring* string)
+ * FUNCTION
+ *   frees contents of a string, if any. Does *not* free the simplestring struct itself.
+ * INPUTS
+ *   string - value containing string to be free'd
+ * RESULT
+ *   void
+ * NOTES
+ *   caller is responsible for allocating and freeing simplestring* struct itself.
+ * SEE ALSO
+ *   simplestring_init ()
+ * SOURCE
+ */
+void simplestring_free(simplestring* string) {
+   if(string && string->str) {
+      my_free(string->str);
+      string->len = 0;
+   }
+}
+/******/
+
+/****f* FUNC/simplestring_addn
+ * NAME
+ *   simplestring_addn
+ * SYNOPSIS
+ *   void simplestring_addn(simplestring* string, const char* add, int add_len)
+ * FUNCTION
+ *   copies n characters from source to target string
+ * INPUTS
+ *   target  - target string
+ *   source  - source string
+ *   add_len - number of characters to copy
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   simplestring_add ()
+ * SOURCE
+ */
+void simplestring_addn(simplestring* target, const char* source, int add_len) {
+   if(target && source) {
+      if(!target->str) {
+         simplestring_init_str(target);
+      }
+      if(target->len + add_len + 1 > target->size) {
+         /* newsize is current length + new length */
+         int newsize = target->len + add_len + 1;
+         int incr = target->size * 2;
+
+         /* align to SIMPLESTRING_INCR increments */
+         newsize = newsize - (newsize % incr) + incr;
+         target->str = (char*)realloc(target->str, newsize);
+
+         target->size = target->str ? newsize : 0;
+      }
+
+      if(target->str) {
+         if(add_len) {
+            memcpy(target->str + target->len, source, add_len);
+         }
+         target->len += add_len;
+         target->str[target->len] = 0; /* null terminate */
+      }
+   }
+}
+/******/
+
+/****f* FUNC/simplestring_add
+ * NAME
+ *   simplestring_add
+ * SYNOPSIS
+ *   void simplestring_add(simplestring* string, const char* add)
+ * FUNCTION
+ *   appends a string of unknown length from source to target
+ * INPUTS
+ *   target - the target string to append to
+ *   source - the source string of unknown length
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   simplestring_addn ()
+ * SOURCE
+ */
+void simplestring_add(simplestring* target, const char* source) {
+   if(target && source) {
+      simplestring_addn(target, source, strlen(source));
+   }
+}
+/******/
+
+
+/*----------------------
+* End String Functions *
+*--------------------**/
diff --git a/php/xmlrpc/libxmlrpc/simplestring.h b/php/xmlrpc/libxmlrpc/simplestring.h
new file mode 100644 (file)
index 0000000..c5d98cf
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+#ifndef __SIMPLESTRING_H__
+ #define __SIMPLESTRING_H__
+
+/*-********************************
+* begin simplestring header stuff *
+**********************************/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+   /****s* struct/simplestring
+ * NAME
+ *  simplestring
+ * NOTES
+ *   represents a string efficiently for fast appending, etc.
+ * SOURCE
+ */
+typedef struct _simplestring {
+   char* str;         /* string buf               */
+   int len;           /* length of string/buf     */
+   int size;          /* size of allocated buffer */
+} simplestring;
+/******/
+
+#ifndef NULL
+ #define NULL 0
+#endif
+
+void simplestring_init(simplestring* string);
+void simplestring_clear(simplestring* string);
+void simplestring_free(simplestring* string);
+void simplestring_add(simplestring* string, const char* add);
+void simplestring_addn(simplestring* string, const char* add, int add_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+/*-******************************
+* end simplestring header stuff *
+********************************/
+
+#endif /* __SIMPLESTRING_H__ */
diff --git a/php/xmlrpc/libxmlrpc/system_methods.c b/php/xmlrpc/libxmlrpc/system_methods.c
new file mode 100644 (file)
index 0000000..c3c2b88
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+/****h* ABOUT/system_methods
+ * AUTHOR
+ *   Dan Libby, aka danda  (dan@libby.com)
+ * HISTORY
+ *   $Log: system_methods.c,v $
+ *   Revision 1.2  2002/07/05 04:43:53  danda
+ *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
+ *
+ *   Revision 1.7  2001/09/29 21:58:05  danda
+ *   adding cvs log to history section
+ *
+ *   4/28/2001 -- danda -- adding system.multicall and separating out system methods.
+ * TODO
+ * NOTES
+ *******/
+
+
+#include "queue.h"
+#include "xmlrpc.h"
+#include "xmlrpc_private.h"
+#include "xmlrpc_introspection_private.h"
+#include "system_methods_private.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+
+static const char* xsm_introspection_xml =
+"<?xml version='1.0' ?>"
+
+"<introspection version='1.0'>"
+ "<typeList>"
+
+ "<typeDescription name='system.value' basetype='struct' desc='description of a value'>"
+   "<value type='string' name='name' optional='yes'>value identifier</value>"
+   "<value type='string' name='type'>value&apos;s xmlrpc or user-defined type</value>"
+   "<value type='string' name='description'>value&apos;s textual description</value> "
+   "<value type='boolean' name='optional'>true if value is optional, else it is required</value> "
+   "<value type='any' name='member' optional='yes'>a child of this element. n/a for scalar types</value> "
+ "</typeDescription>"
+
+ "<typeDescription name='system.valueList' basetype='array' desc='list of value descriptions'>"
+   "<value type='system.value'/>"
+ "</typeDescription>"
+
+ "<typeDescription name='system.stringList' basetype='array' desc='list of strings'>"
+   "<value type='string'/>"
+ "</typeDescription>"
+
+
+ "</typeList>"
+
+ "<methodList>"
+
+ "<!-- system.describeMethods -->"
+ "<methodDescription name='system.describeMethods'>"
+  "<author>Dan Libby</author>"
+  "<purpose>fully describes the methods and types implemented by this XML-RPC server.</purpose>"
+  "<version>1.1</version>"
+  "<signatures>"
+   "<signature>"
+    "<params>"
+     "<value type='array' name='methodList' optional='yes' desc='a list of methods to be described. if omitted, all are described.'>"
+      "<value type='string'>a valid method name</value>"
+     "</value>"
+    "</params>"
+    "<returns>"
+     "<value type='struct' desc='contains methods list and types list'>"
+      "<value type='array' name='methodList' desc='a list of methods'>"
+       "<value type='struct' desc='representation of a single method'>"
+        "<value type='string' name='name'>method name</value>"
+        "<value type='string' name='version' optional='yes'>method version</value>"
+        "<value type='string' name='author' optional='yes'>method author</value>"
+        "<value type='string' name='purpose' optional='yes'>method purpose</value>"
+        "<value type='array' name='signatures' desc='list of method signatures'>"
+         "<value type='struct' desc='representation of a single signature'>"
+          "<value type='system.valueList' name='params' optional='yes'>parameter list</value>"
+          "<value type='system.valueList' name='returns' optional='yes'>return value list</value>"
+         "</value>"
+        "</value>"
+        "<value type='system.stringList' name='bugs' optional='yes'>list of known bugs</value>"
+        "<value type='system.stringList' name='errors' optional='yes'>list of possible errors and error codes</value>"
+        "<value type='system.stringList' name='examples' optional='yes'>list of examples</value>"
+        "<value type='system.stringList' name='history' optional='yes'>list of modifications</value>"
+        "<value type='system.stringList' name='notes' optional='yes'>list of notes</value>"
+        "<value type='system.stringList' name='see' optional='yes'>see also.  list of related methods</value>"
+        "<value type='system.stringList' name='todo' optional='yes'>list of unimplemented features</value>"
+       "</value>"
+      "</value>"
+      "<value type='array' name='typeList' desc='a list of type descriptions. Typically used for referencing complex types'>"
+       "<value type='system.value'>a type description</value>"
+      "</value>"
+     "</value>"
+    "</returns>"
+   "</signature>"
+  "</signatures>"
+  "<see>"
+   "<item name='system.listMethods' />"
+   "<item name='system.methodSignature' />"
+   "<item name='system.methodHelp' />"
+  "</see>"
+  "<example/>"
+  "<error/>"
+  "<note/>"
+  "<bug/>"
+  "<todo/>"
+ "</methodDescription>"
+
+ "<!-- system.listMethods -->"
+ "<methodDescription name='system.listMethods'>"
+  "<author>Dan Libby</author>"
+  "<purpose>enumerates the methods implemented by this XML-RPC server.</purpose>"
+  "<version>1.0</version>"
+  "<signatures>"
+   "<signature>"
+    "<returns>"
+     "<value type='array' desc='an array of strings'>"
+      "<value type='string'>name of a method implemented by the server.</value>"
+     "</value>"
+    "</returns>"
+   "</signature>"
+  "</signatures>"
+  "<see>"
+   "<item name='system.describeMethods' />"
+   "<item name='system.methodSignature' />"
+   "<item name='system.methodHelp' />"
+  "</see>"
+  "<example/>"
+  "<error/>"
+  "<note/>"
+  "<bug/>"
+  "<todo/>"
+ "</methodDescription>"
+
+ "<!-- system.methodHelp -->"
+ "<methodDescription name='system.methodHelp'>"
+  "<author>Dan Libby</author>"
+  "<purpose>provides documentation string for a single method</purpose>"
+  "<version>1.0</version>"
+  "<signatures>"
+   "<signature>"
+    "<params>"
+     "<value type='string' name='methodName'>name of the method for which documentation is desired</value>"
+    "</params>"
+    "<returns>"
+     "<value type='string'>help text if defined for the method passed, otherwise an empty string</value>"
+    "</returns>"
+   "</signature>"
+  "</signatures>"
+  "<see>"
+   "<item name='system.listMethods' />"
+   "<item name='system.methodSignature' />"
+   "<item name='system.methodHelp' />"
+  "</see>"
+  "<example/>"
+  "<error/>"
+  "<note/>"
+  "<bug/>"
+  "<todo/>"
+ "</methodDescription>"
+
+ "<!-- system.methodSignature -->"
+ "<methodDescription name='system.methodSignature'>"
+  "<author>Dan Libby</author>"
+  "<purpose>provides 1 or more signatures for a single method</purpose>"
+  "<version>1.0</version>"
+  "<signatures>"
+   "<signature>"
+    "<params>"
+     "<value type='string' name='methodName'>name of the method for which documentation is desired</value>"
+    "</params>"
+    "<returns>"
+     "<value type='array' desc='a list of arrays, each representing a signature'>"
+      "<value type='array' desc='a list of strings. the first element represents the method return value. subsequent elements represent parameters.'>"
+       "<value type='string'>a string indicating the xmlrpc type of a value. one of: string, int, double, base64, datetime, array, struct</value>"
+      "</value>"
+     "</value>"
+    "</returns>"
+   "</signature>"
+  "</signatures>"
+  "<see>"
+   "<item name='system.listMethods' />"
+   "<item name='system.methodHelp' />"
+   "<item name='system.describeMethods' />"
+  "</see>"
+  "<example/>"
+  "<error/>"
+  "<note/>"
+  "<bug/>"
+  "<todo/>"
+ "</methodDescription>"
+
+ "<!-- system.multiCall -->"
+ "<methodDescription name='system.multiCall'>"
+  "<author>Dan Libby</author>"
+  "<purpose>executes multiple methods in sequence and returns the results</purpose>"
+  "<version>1.0</version>"
+  "<signatures>"
+   "<signature>"
+    "<params>"
+     "<value type='array' name='methodList' desc='an array of method call structs'>"
+      "<value type='struct' desc='a struct representing a single method call'>"
+       "<value type='string' name='methodName' desc='name of the method to be executed'/>"
+       "<value type='array' name='params' desc='an array representing the params to a method. sub-elements should match method signature'/>"
+      "</value>"
+     "</value>"
+    "</params>"
+    "<returns>"
+     "<value type='array' desc='an array of method responses'>"
+      "<value type='array' desc='an array containing a single value, which is the method&apos;s response'/>"
+     "</value>"
+    "</returns>"
+   "</signature>"
+  "</signatures>"
+  "<see>"
+   "<item name='system.listMethods' />"
+   "<item name='system.methodHelp' />"
+   "<item name='system.describeMethods' />"
+  "</see>"
+  "<example/>"
+  "<error/>"
+  "<note/>"
+  "<bug/>"
+  "<todo/>"
+ "</methodDescription>"
+
+ "<!-- system.getCapabilities -->"
+ "<methodDescription name='system.getCapabilities'>"
+  "<author>Dan Libby</author>"
+  "<purpose>returns a list of capabilities supported by this server</purpose>"
+  "<version>1.0</version>"
+  "<notes><item>spec url: http://groups.yahoo.com/group/xml-rpc/message/2897</item></notes>"
+  "<signatures>"
+   "<signature>"
+    "<returns>"
+     "<value type='struct' desc='list of capabilities, each with a unique key defined by the capability&apos;s spec'>"
+      "<value type='struct' desc='definition of a single capability'>"
+       "<value type='string' name='specURL'>www address of the specification defining this capability</value>"
+       "<value type='int' name='specVersion'>version of the spec that this server's implementation conforms to</value>"
+      "</value>"
+     "</value>"
+    "</returns>"
+   "</signature>"
+  "</signatures>"
+  "<see>"
+   "<item name='system.listMethods' />"
+   "<item name='system.methodHelp' />"
+   "<item name='system.describeMethods' />"
+  "</see>"
+  "<example/>"
+  "<error/>"
+  "<note/>"
+  "<bug/>"
+  "<todo/>"
+ "</methodDescription>"
+
+ "</methodList>"
+"</introspection>";
+
+
+/* forward declarations for static (non public, non api) funcs */
+static XMLRPC_VALUE xsm_system_multicall_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+static XMLRPC_VALUE xsm_system_get_capabilities_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+
+/*-*******************
+* System Methods API *
+*********************/
+
+static void xsm_lazy_doc_methods_cb(XMLRPC_SERVER server, void* userData) {
+   XMLRPC_VALUE xDesc = XMLRPC_IntrospectionCreateDescription(xsm_introspection_xml, NULL);
+   XMLRPC_ServerAddIntrospectionData(server, xDesc);
+   XMLRPC_CleanupValue(xDesc);
+}
+
+void xsm_register(XMLRPC_SERVER server) {
+   xi_register_system_methods(server);
+
+   XMLRPC_ServerRegisterMethod(server, xsm_token_system_multicall, xsm_system_multicall_cb);
+   XMLRPC_ServerRegisterMethod(server, xsm_token_system_get_capabilities, xsm_system_get_capabilities_cb);
+
+   /* callback for documentation generation should it be requested */
+   XMLRPC_ServerRegisterIntrospectionCallback(server, xsm_lazy_doc_methods_cb);
+}
+
+XMLRPC_VALUE xsm_system_multicall_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   XMLRPC_VALUE xArray = XMLRPC_VectorRewind(XMLRPC_RequestGetData(input));
+   XMLRPC_VALUE xReturn = XMLRPC_CreateVector(0, xmlrpc_vector_array);
+
+   if (xArray) {
+      XMLRPC_VALUE xMethodIter = XMLRPC_VectorRewind(xArray);
+
+      while (xMethodIter) {
+         XMLRPC_REQUEST request = XMLRPC_RequestNew();
+         if(request) {
+            const char* methodName = XMLRPC_VectorGetStringWithID(xMethodIter, "methodName");
+            XMLRPC_VALUE params = XMLRPC_VectorGetValueWithID(xMethodIter, "params");
+
+            if(methodName && params) {
+               XMLRPC_VALUE xRandomArray = XMLRPC_CreateVector(0, xmlrpc_vector_array);
+               XMLRPC_RequestSetMethodName(request, methodName);
+               XMLRPC_RequestSetData(request, params);
+               XMLRPC_RequestSetRequestType(request, xmlrpc_request_call);
+
+               XMLRPC_AddValueToVector(xRandomArray, 
+                                       XMLRPC_ServerCallMethod(server, request, userData));
+
+               XMLRPC_AddValueToVector(xReturn, xRandomArray);
+            }
+            XMLRPC_RequestFree(request, 1);
+         }
+         xMethodIter = XMLRPC_VectorNext(xArray);
+      }
+   }
+   return xReturn;
+}
+
+
+XMLRPC_VALUE xsm_system_get_capabilities_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   XMLRPC_VALUE xReturn = XMLRPC_CreateVector(0, xmlrpc_vector_struct);
+   XMLRPC_VALUE xFaults = XMLRPC_CreateVector("faults_interop", xmlrpc_vector_struct);
+   XMLRPC_VALUE xIntro = XMLRPC_CreateVector("introspection", xmlrpc_vector_struct);
+
+   /* support for fault spec */
+   XMLRPC_VectorAppendString(xFaults, "specURL", "http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php", 0);
+   XMLRPC_VectorAppendInt(xFaults, "specVersion", 20010516);
+
+   /* support for introspection spec */
+   XMLRPC_VectorAppendString(xIntro, "specURL", "http://xmlrpc-epi.sourceforge.net/specs/rfc.introspection.php", 0);
+   XMLRPC_VectorAppendInt(xIntro, "specVersion", 20010516);
+
+   XMLRPC_AddValuesToVector(xReturn,
+                            xFaults,
+                            xIntro,
+                            NULL);
+
+   return xReturn;
+                            
+}
+
+/*-***********************
+* End System Methods API *
+*************************/
+
+
+
diff --git a/php/xmlrpc/libxmlrpc/system_methods_private.h b/php/xmlrpc/libxmlrpc/system_methods_private.h
new file mode 100644 (file)
index 0000000..72408fd
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Dan Libby, Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+/* IMPORTANT!
+ *
+ * only non-public things should be in this file.  It is fine for any .c file
+ * in xmlrpc/src to include it, but users of the public API should never
+ * include it, and thus *.h files that are part of the public API should
+ * never include it, or they would break if this file is not present.
+ */
+
+
+#ifndef __SYSTEM_METHODS_PRIVATE_H
+/*
+ * Avoid include redundancy.
+ */
+#define __SYSTEM_METHODS_PRIVATE_H
+
+/*----------------------------------------------------------------------------
+ * system_methods_private.h
+ *
+ * Purpose:
+ *   define non-public system.* methods
+ * Comments:
+ *   xsm = xmlrpc system methods
+ */
+
+/*----------------------------------------------------------------------------
+ * Constants
+ */
+#define xsm_token_system_multicall "system.multiCall"
+#define xsm_token_system_get_capabilities "system.getCapabilities"
+
+
+/*----------------------------------------------------------------------------
+ * Includes
+ */
+
+/*----------------------------------------------------------------------------
+ * Structures
+ */
+/*----------------------------------------------------------------------------
+ * Globals
+ */
+
+/*----------------------------------------------------------------------------
+ * Functions
+ */
+void xsm_register(XMLRPC_SERVER server);
+int xsm_is_system_method(XMLRPC_Callback cb);
+/*----------------------------------------------------------------------------
+ * Macros
+ */
+
+#endif /* __SYSTEM_METHODS_PRIVATE_H */
+
+
+
+
diff --git a/php/xmlrpc/libxmlrpc/xml_element.c b/php/xmlrpc/libxmlrpc/xml_element.c
new file mode 100644 (file)
index 0000000..904ba15
--- /dev/null
@@ -0,0 +1,750 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+static const char rcsid[] = "#(@) $Id: xml_element.c 5574 2007-10-25 20:33:17Z thierry $";
+
+
+
+/****h* ABOUT/xml_element
+ * NAME
+ *   xml_element
+ * AUTHOR
+ *   Dan Libby, aka danda  (dan@libby.com)
+ * CREATION DATE
+ *   06/2000
+ * HISTORY
+ *   $Log: xml_element.c,v $
+ *   Revision 1.6  2004/06/01 20:16:06  iliaa
+ *   Fixed bug #28597 (xmlrpc_encode_request() incorrectly encodes chars in
+ *   200-210 range).
+ *   Patch by: fernando dot nemec at folha dot com dot br
+ *
+ *   Revision 1.5  2003/12/16 21:00:21  sniper
+ *   Fix some compile warnings (patch by Joe Orton)
+ *
+ *   Revision 1.4  2002/11/26 23:01:16  fmk
+ *   removing unused variables
+ *
+ *   Revision 1.3  2002/07/05 04:43:53  danda
+ *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
+ *
+ *   Revision 1.9  2002/07/03 20:54:30  danda
+ *   root element should not have a parent. patch from anon SF user
+ *
+ *   Revision 1.8  2002/05/23 17:46:51  danda
+ *   patch from mukund - fix non utf-8 encoding conversions
+ *
+ *   Revision 1.7  2002/02/13 20:58:50  danda
+ *   patch to make source more windows friendly, contributed by Jeff Lawson
+ *
+ *   Revision 1.6  2002/01/08 01:06:55  danda
+ *   enable <?xml version="1.0"?> format for parsers that are very picky.
+ *
+ *   Revision 1.5  2001/09/29 21:58:05  danda
+ *   adding cvs log to history section
+ *
+ *   10/15/2000 -- danda -- adding robodoc documentation
+ * TODO
+ *   Nicer external API. Get rid of macros.  Make opaque types, etc.
+ * PORTABILITY
+ *   Coded on RedHat Linux 6.2.  Builds on Solaris x86.  Should build on just
+ *   about anything with minor mods.
+ * NOTES
+ *   This code incorporates ideas from expat-ensor from http://xml.ensor.org.
+ *  
+ *   It was coded primarily to act as a go-between for expat and xmlrpc. To this
+ *   end, it stores xml elements, their sub-elements, and their attributes in an
+ *   in-memory tree.  When expat is done parsing, the tree can be walked, thus
+ *   retrieving the values.  The code can also be used to build a tree via API then
+ *   write out the tree to a buffer, thus "serializing" the xml.
+ *
+ *   It turns out this is useful for other purposes, such as parsing config files.
+ *   YMMV.
+ *
+ *   Some Features:
+ *     - output option for xml escaping data.  Choices include no escaping, entity escaping,
+ *       or CDATA sections.
+ *     - output option for character encoding.  Defaults to (none) utf-8.
+ *     - output option for verbosity/readability.  ultra-compact, newlines, pretty/level indented. 
+ *
+ * BUGS
+ *   there must be some.
+ ******/
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "xml_element.h"
+#include "queue.h"
+#include "expat.h"
+#include "encodings.h"
+
+#define my_free(thing)  if(thing) {free(thing); thing = NULL;}
+
+#define XML_DECL_START                 "<?xml"
+#define XML_DECL_START_LEN             sizeof(XML_DECL_START) - 1
+#define XML_DECL_VERSION               "version=\"1.0\""
+#define XML_DECL_VERSION_LEN           sizeof(XML_DECL_VERSION) - 1
+#define XML_DECL_ENCODING_ATTR         "encoding"
+#define XML_DECL_ENCODING_ATTR_LEN     sizeof(XML_DECL_ENCODING_ATTR) - 1
+#define XML_DECL_ENCODING_DEFAULT      "utf-8"
+#define XML_DECL_ENCODING_DEFAULT_LEN  sizeof(XML_DECL_ENCODING_DEFAULT) - 1
+#define XML_DECL_END                   "?>"
+#define XML_DECL_END_LEN               sizeof(XML_DECL_END) - 1
+#define START_TOKEN_BEGIN              "<"
+#define START_TOKEN_BEGIN_LEN          sizeof(START_TOKEN_BEGIN) - 1
+#define START_TOKEN_END                ">"
+#define START_TOKEN_END_LEN            sizeof(START_TOKEN_END) - 1
+#define EMPTY_START_TOKEN_END          "/>"
+#define EMPTY_START_TOKEN_END_LEN      sizeof(EMPTY_START_TOKEN_END) - 1
+#define END_TOKEN_BEGIN                "</"
+#define END_TOKEN_BEGIN_LEN            sizeof(END_TOKEN_BEGIN) - 1
+#define END_TOKEN_END                  ">"
+#define END_TOKEN_END_LEN              sizeof(END_TOKEN_END) - 1
+#define ATTR_DELIMITER                 "\""
+#define ATTR_DELIMITER_LEN             sizeof(ATTR_DELIMITER) - 1
+#define CDATA_BEGIN                    "<![CDATA["
+#define CDATA_BEGIN_LEN                sizeof(CDATA_BEGIN) - 1
+#define CDATA_END                      "]]>"
+#define CDATA_END_LEN                  sizeof(CDATA_END) - 1
+#define EQUALS                         "="
+#define EQUALS_LEN                     sizeof(EQUALS) - 1
+#define WHITESPACE                     " "
+#define WHITESPACE_LEN                 sizeof(WHITESPACE) - 1
+#define NEWLINE                        "\n"
+#define NEWLINE_LEN                    sizeof(NEWLINE) - 1
+#define MAX_VAL_BUF                    144
+#define SCALAR_STR                     "SCALAR"
+#define SCALAR_STR_LEN                 sizeof(SCALAR_STR) - 1
+#define VECTOR_STR                     "VECTOR"
+#define VECTOR_STR_LEN                 sizeof(VECTOR_STR) - 1
+#define RESPONSE_STR                   "RESPONSE"
+#define RESPONSE_STR_LEN               sizeof(RESPONSE_STR) - 1
+
+
+/*-----------------------------
+- Begin xml_element Functions -
+-----------------------------*/
+
+/****f* xml_element/xml_elem_free_non_recurse
+ * NAME
+ *   xml_elem_free_non_recurse
+ * SYNOPSIS
+ *   void xml_elem_free_non_recurse(xml_element* root)
+ * FUNCTION
+ *   free a single xml element.  child elements will not be freed.
+ * INPUTS
+ *   root - the element to free
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_free ()
+ *   xml_elem_new ()
+ * SOURCE
+ */
+void xml_elem_free_non_recurse(xml_element* root) {
+   if(root) {
+      xml_element_attr* attrs = Q_Head(&root->attrs);
+      while(attrs) {
+         my_free(attrs->key);
+         my_free(attrs->val);
+         my_free(attrs);
+         attrs = Q_Next(&root->attrs);
+      }
+
+      Q_Destroy(&root->children);
+      Q_Destroy(&root->attrs);
+      if (root->name) {
+          free((char *)root->name);
+          root->name = NULL;
+      }
+      simplestring_free(&root->text);
+      my_free(root);
+   }
+}
+/******/
+
+/****f* xml_element/xml_elem_free
+ * NAME
+ *   xml_elem_free
+ * SYNOPSIS
+ *   void xml_elem_free(xml_element* root)
+ * FUNCTION
+ *   free an xml element and all of its child elements
+ * INPUTS
+ *   root - the root of an xml tree you would like to free
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_free_non_recurse ()
+ *   xml_elem_new ()
+ * SOURCE
+ */
+void xml_elem_free(xml_element* root) {
+   if(root) {
+      xml_element* kids = Q_Head(&root->children);
+      while(kids) {
+         xml_elem_free(kids);
+         kids = Q_Next(&root->children);
+      }
+      xml_elem_free_non_recurse(root);
+   }
+}
+/******/
+
+/****f* xml_element/xml_elem_new
+ * NAME
+ *   xml_elem_new
+ * SYNOPSIS
+ *   xml_element* xml_elem_new()
+ * FUNCTION
+ *   allocates and initializes a new xml_element
+ * INPUTS
+ *   none
+ * RESULT
+ *   xml_element* or NULL.  NULL indicates an out-of-memory condition.
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_free ()
+ *   xml_elem_free_non_recurse ()
+ * SOURCE
+ */
+xml_element* xml_elem_new() {
+   xml_element* elem = calloc(1, sizeof(xml_element));
+   if(elem) {
+      Q_Init(&elem->children);
+      Q_Init(&elem->attrs);
+      simplestring_init(&elem->text);
+
+      /* init empty string in case we don't find any char data */
+      simplestring_addn(&elem->text, "", 0);
+   }
+   return elem;
+}
+/******/
+
+static int xml_elem_writefunc(int (*fptr)(void *data, const char *text, int size), const char *text, void *data, int len)
+{
+   return fptr && text ? fptr(data, text, len ? len : strlen(text)) : 0;
+}
+
+
+
+static int create_xml_escape(char *pString, unsigned char c)
+{ 
+  int counter = 0;
+
+  pString[counter++] = '&';
+  pString[counter++] = '#';
+  if(c >= 100) {
+    pString[counter++] = c / 100 + '0';
+    c = c % 100;
+  }
+  pString[counter++] = c / 10 + '0';
+  c = c % 10;
+
+  pString[counter++] = c + '0';
+  pString[counter++] = ';';
+  return counter; 
+}
+
+#define non_ascii(c) (c > 127)
+#define non_print(c) (!isprint(c))
+#define markup(c) (c == '&' || c == '\"' || c == '>' || c == '<')
+#define entity_length(c) ( (c >= 100) ? 3 : ((c >= 10) ? 2 : 1) ) + 3; /* "&#" + c + ";" */
+
+/*
+ * xml_elem_entity_escape
+ *
+ * Purpose:
+ *   escape reserved xml chars and non utf-8 chars as xml entities
+ * Comments:
+ *   The return value may be a new string, or null if no
+ *     conversion was performed. In the latter case, *newlen will
+ *     be 0.
+ * Flags (to escape)
+ *  xml_elem_no_escaping             = 0x000,
+ *  xml_elem_entity_escaping         = 0x002,   // escape xml special chars as entities
+ *  xml_elem_non_ascii_escaping      = 0x008,   // escape chars above 127
+ *  xml_elem_cdata_escaping          = 0x010,   // wrap in cdata
+ */
+static char* xml_elem_entity_escape(const char* buf, int old_len, int *newlen, XML_ELEM_ESCAPING flags) {
+  char *pRetval = 0;
+  int iNewBufLen=0;
+
+#define should_escape(c, flag) ( ((flag & xml_elem_markup_escaping) && markup(c)) || \
+                                 ((flag & xml_elem_non_ascii_escaping) && non_ascii(c)) || \
+                                 ((flag & xml_elem_non_print_escaping) && non_print(c)) )
+
+  if(buf && *buf) {
+    const unsigned char *bufcopy;
+    char *NewBuffer;
+    int ToBeXmlEscaped=0;
+    int iLength;
+    bufcopy = buf;
+    iLength= old_len ? old_len : strlen(buf);
+    while(*bufcopy) {
+      if( should_escape(*bufcopy, flags) ) {
+       /* the length will increase by length of xml escape - the character length */
+       iLength += entity_length(*bufcopy);
+       ToBeXmlEscaped=1;
+      }
+      bufcopy++;
+    }
+
+    if(ToBeXmlEscaped) {
+
+      NewBuffer= malloc(iLength+1);
+      if(NewBuffer) {
+       bufcopy=buf;
+       while(*bufcopy) {
+         if(should_escape(*bufcopy, flags)) {
+           iNewBufLen += create_xml_escape(NewBuffer+iNewBufLen,*bufcopy);
+         }
+         else {
+           NewBuffer[iNewBufLen++]=*bufcopy;
+         }
+         bufcopy++;
+       }
+       NewBuffer[iNewBufLen] = 0;
+       pRetval = NewBuffer;
+      }
+    }
+  }
+
+  if(newlen) {
+     *newlen = iNewBufLen;
+  }
+
+  return pRetval;
+}
+
+
+static void xml_element_serialize(xml_element *el, int (*fptr)(void *data, const char *text, int size), void *data, XML_ELEM_OUTPUT_OPTIONS options, int depth)
+{
+   int i;
+   static STRUCT_XML_ELEM_OUTPUT_OPTIONS default_opts = {xml_elem_pretty, xml_elem_markup_escaping | xml_elem_non_print_escaping, XML_DECL_ENCODING_DEFAULT};
+   static char whitespace[] = "                                                                                               "
+                              "                                                                                               "
+                              "                                                                                               ";
+   depth++;
+
+   if(!el) {
+      fprintf(stderr, "Nothing to write\n");
+      return;
+   }
+   if(!options) {
+      options = &default_opts;
+   }
+
+   /* print xml declaration if at root level */
+   if(depth == 1) {
+      xml_elem_writefunc(fptr, XML_DECL_START, data, XML_DECL_START_LEN);
+      xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN);
+      xml_elem_writefunc(fptr, XML_DECL_VERSION, data, XML_DECL_VERSION_LEN);
+      if(options->encoding && *options->encoding) {
+          xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN);
+          xml_elem_writefunc(fptr, XML_DECL_ENCODING_ATTR, data, XML_DECL_ENCODING_ATTR_LEN);
+          xml_elem_writefunc(fptr, EQUALS, data, EQUALS_LEN);
+          xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+          xml_elem_writefunc(fptr, options->encoding, data, 0);
+          xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+      }
+      xml_elem_writefunc(fptr, XML_DECL_END, data, XML_DECL_END_LEN);
+      if(options->verbosity != xml_elem_no_white_space) {
+         xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN);
+      }
+   }
+
+   if(options->verbosity == xml_elem_pretty && depth > 2) {
+         xml_elem_writefunc(fptr, whitespace, data, depth - 2);
+   }
+   /* begin element */
+   xml_elem_writefunc(fptr,START_TOKEN_BEGIN, data, START_TOKEN_BEGIN_LEN);
+   if(el->name) {
+      xml_elem_writefunc(fptr, el->name, data, 0);
+
+      /* write attrs, if any */
+      if(Q_Size(&el->attrs)) {
+         xml_element_attr* iter = Q_Head(&el->attrs);
+         while( iter ) {
+            xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN);
+            xml_elem_writefunc(fptr, iter->key, data, 0);
+            xml_elem_writefunc(fptr, EQUALS, data, EQUALS_LEN);
+            xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+            xml_elem_writefunc(fptr, iter->val, data, 0);
+            xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+
+            iter = Q_Next(&el->attrs);
+         }
+      }
+   }
+   else {
+      xml_elem_writefunc(fptr, "None", data, 0);
+   }
+   /* if no text and no children, use abbreviated form, eg: <foo/> */
+   if(!el->text.len && !Q_Size(&el->children)) {
+       xml_elem_writefunc(fptr, EMPTY_START_TOKEN_END, data, EMPTY_START_TOKEN_END_LEN);
+   }
+   /* otherwise, print element contents */
+   else {
+       xml_elem_writefunc(fptr, START_TOKEN_END, data, START_TOKEN_END_LEN);
+
+       /* print text, if any */
+       if(el->text.len) {
+          char* escaped_str = el->text.str;
+          int buflen = el->text.len;
+
+          if(options->escaping && options->escaping != xml_elem_cdata_escaping) {
+             escaped_str = xml_elem_entity_escape(el->text.str, buflen, &buflen, options->escaping );
+             if(!escaped_str) {
+                escaped_str = el->text.str;
+             }
+          }
+
+          if(options->escaping & xml_elem_cdata_escaping) {
+             xml_elem_writefunc(fptr, CDATA_BEGIN, data, CDATA_BEGIN_LEN);
+          }
+
+          xml_elem_writefunc(fptr, escaped_str, data, buflen);
+
+          if(escaped_str != el->text.str) {
+             my_free(escaped_str);
+          }
+
+          if(options->escaping & xml_elem_cdata_escaping) {
+             xml_elem_writefunc(fptr, CDATA_END, data, CDATA_END_LEN);
+          }
+       }
+       /* no text, so print child elems */
+       else {
+          xml_element *kids = Q_Head(&el->children);
+          i = 0;
+          while( kids ) {
+             if(i++ == 0) {
+                if(options->verbosity != xml_elem_no_white_space) {
+                   xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN);
+                }
+             }
+             xml_element_serialize(kids, fptr, data, options, depth);
+             kids = Q_Next(&el->children);
+          }
+          if(i) {
+             if(options->verbosity == xml_elem_pretty && depth > 2) {
+                   xml_elem_writefunc(fptr, whitespace, data, depth - 2);
+             }
+          }
+       }
+
+       xml_elem_writefunc(fptr, END_TOKEN_BEGIN, data, END_TOKEN_BEGIN_LEN);
+       xml_elem_writefunc(fptr,el->name ? el->name : "None", data, 0);
+       xml_elem_writefunc(fptr, END_TOKEN_END, data, END_TOKEN_END_LEN);
+   }
+   if(options->verbosity != xml_elem_no_white_space) {
+      xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN);
+   }
+}
+
+/* print buf to file */
+static int file_out_fptr(void *f, const char *text, int size)
+{
+   fputs(text, (FILE *)f);
+   return 0;
+}
+
+/* print buf to simplestring */
+static int simplestring_out_fptr(void *f, const char *text, int size)
+{
+   simplestring* buf = (simplestring*)f;
+   if(buf) {
+      simplestring_addn(buf, text, size);
+   }
+   return 0;
+}
+
+/****f* xml_element/xml_elem_serialize_to_string
+ * NAME
+ *   xml_elem_serialize_to_string
+ * SYNOPSIS
+ *   void xml_element_serialize_to_string(xml_element *el, XML_ELEM_OUTPUT_OPTIONS options, int *buf_len)
+ * FUNCTION
+ *   writes element tree as XML into a newly allocated buffer
+ * INPUTS
+ *   el      - root element of tree
+ *   options - options determining how output is written.  see XML_ELEM_OUTPUT_OPTIONS
+ *   buf_len - length of returned buffer, if not null.
+ * RESULT
+ *   char* or NULL. Must be free'd by caller.
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_serialize_to_stream ()
+ *   xml_elem_parse_buf ()
+ * SOURCE
+ */
+char* xml_elem_serialize_to_string(xml_element *el, XML_ELEM_OUTPUT_OPTIONS options, int *buf_len)
+{
+   simplestring buf;
+   simplestring_init(&buf);
+
+   xml_element_serialize(el, simplestring_out_fptr, (void *)&buf, options, 0);
+
+   if(buf_len) {
+      *buf_len = buf.len;
+   }
+
+   return buf.str;
+}
+/******/
+
+/****f* xml_element/xml_elem_serialize_to_stream
+ * NAME
+ *   xml_elem_serialize_to_stream
+ * SYNOPSIS
+ *   void xml_elem_serialize_to_stream(xml_element *el, FILE *output, XML_ELEM_OUTPUT_OPTIONS options)
+ * FUNCTION
+ *   writes element tree as XML into a stream (typically an opened file)
+ * INPUTS
+ *   el      - root element of tree
+ *   output  - stream handle
+ *   options - options determining how output is written.  see XML_ELEM_OUTPUT_OPTIONS
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_serialize_to_string ()
+ *   xml_elem_parse_buf ()
+ * SOURCE
+ */
+void xml_elem_serialize_to_stream(xml_element *el, FILE *output, XML_ELEM_OUTPUT_OPTIONS options)
+{
+   xml_element_serialize(el, file_out_fptr, (void *)output, options, 0);
+}
+/******/
+
+/*--------------------------*
+* End xml_element Functions *
+*--------------------------*/
+
+
+/*----------------------
+* Begin Expat Handlers *
+*---------------------*/
+
+typedef struct _xml_elem_data {
+   xml_element*           root;
+   xml_element*           current;
+   XML_ELEM_INPUT_OPTIONS input_options;
+   int                    needs_enc_conversion;
+} xml_elem_data;
+
+
+/* expat start of element handler */
+static void startElement(void *userData, const char *name, const char **attrs)
+{
+   xml_element *c;
+   xml_elem_data* mydata = (xml_elem_data*)userData;
+   const char** p = attrs;
+
+   if(mydata) {
+      c = mydata->current;
+
+      mydata->current = xml_elem_new();
+      mydata->current->name = (char*)strdup(name);
+      mydata->current->parent = c;
+
+      /* init attrs */
+      while(p && *p) {
+         xml_element_attr* attr = malloc(sizeof(xml_element_attr));
+         if(attr) {
+            attr->key = strdup(*p);
+            attr->val = strdup(*(p+1));
+            Q_PushTail(&mydata->current->attrs, attr);
+
+            p += 2;
+         }
+      }
+   }
+}
+
+/* expat end of element handler */
+static void endElement(void *userData, const char *name)
+{
+   xml_elem_data* mydata = (xml_elem_data*)userData;
+
+   if(mydata && mydata->current && mydata->current->parent) {
+      Q_PushTail(&mydata->current->parent->children, mydata->current);
+
+      mydata->current = mydata->current->parent;
+   }
+}
+
+/* expat char data handler */
+static void charHandler(void *userData,
+                        const char *s,
+                        int len)
+{
+   xml_elem_data* mydata = (xml_elem_data*)userData;
+   if(mydata && mydata->current) {
+
+      /* Check if we need to decode utf-8 parser output to another encoding */
+      if(mydata->needs_enc_conversion && mydata->input_options->encoding) {
+         int new_len = 0;
+         char* add_text = utf8_decode(s, len, &new_len, mydata->input_options->encoding);
+         if(add_text) {
+            len = new_len;
+            simplestring_addn(&mydata->current->text, add_text, len);
+            free(add_text);
+            return;
+         }
+      }
+      simplestring_addn(&mydata->current->text, s, len);
+   }
+}
+/******/
+
+/*-------------------*
+* End Expat Handlers *
+*-------------------*/
+
+/*-------------------*
+* xml_elem_parse_buf *
+*-------------------*/
+
+/****f* xml_element/xml_elem_parse_buf
+ * NAME
+ *   xml_elem_parse_buf
+ * SYNOPSIS
+ *   xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTIONS options, XML_ELEM_ERROR error)
+ * FUNCTION
+ *   parse a buffer containing XML into an xml_element in-memory tree
+ * INPUTS
+ *   in_buf   - buffer containing XML document
+ *   len      - length of buffer
+ *   options  - input options. optional
+ *   error    - error result data. optional. check if result is null.
+ * RESULT
+ *   void
+ * NOTES
+ *   The returned data must be free'd by caller
+ * SEE ALSO
+ *   xml_elem_serialize_to_string ()
+ *   xml_elem_free ()
+ * SOURCE
+ */
+xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTIONS options, XML_ELEM_ERROR error)
+{
+   xml_element* xReturn = NULL;
+   char buf[100] = "";
+   static STRUCT_XML_ELEM_INPUT_OPTIONS default_opts = {encoding_utf_8};
+
+   if(!options) {
+      options = &default_opts;
+   }
+
+   if(in_buf) {
+      XML_Parser parser;
+      xml_elem_data mydata = {0};
+
+      parser = XML_ParserCreate(NULL);
+
+      mydata.root = xml_elem_new();
+      mydata.current = mydata.root;
+      mydata.input_options = options;
+      mydata.needs_enc_conversion = options->encoding && strcmp(options->encoding, encoding_utf_8);
+
+      XML_SetElementHandler(parser, startElement, endElement);
+      XML_SetCharacterDataHandler(parser, charHandler);
+
+      /* pass the xml_elem_data struct along */
+      XML_SetUserData(parser, (void*)&mydata);
+
+      if(!len) {
+         len = strlen(in_buf);
+      }
+
+      /* parse the XML */
+      if(XML_Parse(parser, in_buf, len, 1) == 0) {
+         enum XML_Error err_code = XML_GetErrorCode(parser);
+         int line_num = XML_GetCurrentLineNumber(parser);
+         int col_num = XML_GetCurrentColumnNumber(parser);
+         long byte_idx = XML_GetCurrentByteIndex(parser);
+         int byte_total = XML_GetCurrentByteCount(parser);
+         const char * error_str = XML_ErrorString(err_code);
+         if(byte_idx >= 0) {
+             snprintf(buf, 
+                      sizeof(buf),
+                      "\n\tdata beginning %ld before byte index: %s\n",
+                      byte_idx > 10  ? 10 : byte_idx,
+                      in_buf + (byte_idx > 10 ? byte_idx - 10 : byte_idx));
+         }
+
+         fprintf(stderr, "expat reports error code %i\n"
+                "\tdescription: %s\n"
+                "\tline: %i\n"
+                "\tcolumn: %i\n"
+                "\tbyte index: %ld\n"
+                "\ttotal bytes: %i\n%s ",
+                err_code, error_str, line_num, 
+                col_num, byte_idx, byte_total, buf);
+
+
+          /* error condition */
+          if(error) {
+              error->parser_code = (long)err_code;
+              error->line = line_num;
+              error->column = col_num;
+              error->byte_index = byte_idx;
+              error->parser_error = error_str;
+          }
+      }
+      else {
+         xReturn = (xml_element*)Q_Head(&mydata.root->children);
+         xReturn->parent = NULL;
+      }
+
+      XML_ParserFree(parser);
+
+
+      xml_elem_free_non_recurse(mydata.root);
+   }
+
+   return xReturn;
+}
+
+/******/
diff --git a/php/xmlrpc/libxmlrpc/xml_element.c.gcc4 b/php/xmlrpc/libxmlrpc/xml_element.c.gcc4
new file mode 100644 (file)
index 0000000..a90b2d2
--- /dev/null
@@ -0,0 +1,747 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+static const char rcsid[] = "#(@) $Id: xml_element.c.gcc4 5574 2007-10-25 20:33:17Z thierry $";
+
+
+
+/****h* ABOUT/xml_element
+ * NAME
+ *   xml_element
+ * AUTHOR
+ *   Dan Libby, aka danda  (dan@libby.com)
+ * CREATION DATE
+ *   06/2000
+ * HISTORY
+ *   $Log: xml_element.c,v $
+ *   Revision 1.6  2004/06/01 20:16:06  iliaa
+ *   Fixed bug #28597 (xmlrpc_encode_request() incorrectly encodes chars in
+ *   200-210 range).
+ *   Patch by: fernando dot nemec at folha dot com dot br
+ *
+ *   Revision 1.5  2003/12/16 21:00:21  sniper
+ *   Fix some compile warnings (patch by Joe Orton)
+ *
+ *   Revision 1.4  2002/11/26 23:01:16  fmk
+ *   removing unused variables
+ *
+ *   Revision 1.3  2002/07/05 04:43:53  danda
+ *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
+ *
+ *   Revision 1.9  2002/07/03 20:54:30  danda
+ *   root element should not have a parent. patch from anon SF user
+ *
+ *   Revision 1.8  2002/05/23 17:46:51  danda
+ *   patch from mukund - fix non utf-8 encoding conversions
+ *
+ *   Revision 1.7  2002/02/13 20:58:50  danda
+ *   patch to make source more windows friendly, contributed by Jeff Lawson
+ *
+ *   Revision 1.6  2002/01/08 01:06:55  danda
+ *   enable <?xml version="1.0"?> format for parsers that are very picky.
+ *
+ *   Revision 1.5  2001/09/29 21:58:05  danda
+ *   adding cvs log to history section
+ *
+ *   10/15/2000 -- danda -- adding robodoc documentation
+ * TODO
+ *   Nicer external API. Get rid of macros.  Make opaque types, etc.
+ * PORTABILITY
+ *   Coded on RedHat Linux 6.2.  Builds on Solaris x86.  Should build on just
+ *   about anything with minor mods.
+ * NOTES
+ *   This code incorporates ideas from expat-ensor from http://xml.ensor.org.
+ *  
+ *   It was coded primarily to act as a go-between for expat and xmlrpc. To this
+ *   end, it stores xml elements, their sub-elements, and their attributes in an
+ *   in-memory tree.  When expat is done parsing, the tree can be walked, thus
+ *   retrieving the values.  The code can also be used to build a tree via API then
+ *   write out the tree to a buffer, thus "serializing" the xml.
+ *
+ *   It turns out this is useful for other purposes, such as parsing config files.
+ *   YMMV.
+ *
+ *   Some Features:
+ *     - output option for xml escaping data.  Choices include no escaping, entity escaping,
+ *       or CDATA sections.
+ *     - output option for character encoding.  Defaults to (none) utf-8.
+ *     - output option for verbosity/readability.  ultra-compact, newlines, pretty/level indented. 
+ *
+ * BUGS
+ *   there must be some.
+ ******/
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "xml_element.h"
+#include "queue.h"
+#include "expat.h"
+#include "encodings.h"
+
+#define my_free(thing)  if(thing) {free(thing); thing = 0;}
+
+#define XML_DECL_START                 "<?xml"
+#define XML_DECL_START_LEN             sizeof(XML_DECL_START) - 1
+#define XML_DECL_VERSION               "version=\"1.0\""
+#define XML_DECL_VERSION_LEN           sizeof(XML_DECL_VERSION) - 1
+#define XML_DECL_ENCODING_ATTR         "encoding"
+#define XML_DECL_ENCODING_ATTR_LEN     sizeof(XML_DECL_ENCODING_ATTR) - 1
+#define XML_DECL_ENCODING_DEFAULT      "utf-8"
+#define XML_DECL_ENCODING_DEFAULT_LEN  sizeof(XML_DECL_ENCODING_DEFAULT) - 1
+#define XML_DECL_END                   "?>"
+#define XML_DECL_END_LEN               sizeof(XML_DECL_END) - 1
+#define START_TOKEN_BEGIN              "<"
+#define START_TOKEN_BEGIN_LEN          sizeof(START_TOKEN_BEGIN) - 1
+#define START_TOKEN_END                ">"
+#define START_TOKEN_END_LEN            sizeof(START_TOKEN_END) - 1
+#define EMPTY_START_TOKEN_END          "/>"
+#define EMPTY_START_TOKEN_END_LEN      sizeof(EMPTY_START_TOKEN_END) - 1
+#define END_TOKEN_BEGIN                "</"
+#define END_TOKEN_BEGIN_LEN            sizeof(END_TOKEN_BEGIN) - 1
+#define END_TOKEN_END                  ">"
+#define END_TOKEN_END_LEN              sizeof(END_TOKEN_END) - 1
+#define ATTR_DELIMITER                 "\""
+#define ATTR_DELIMITER_LEN             sizeof(ATTR_DELIMITER) - 1
+#define CDATA_BEGIN                    "<![CDATA["
+#define CDATA_BEGIN_LEN                sizeof(CDATA_BEGIN) - 1
+#define CDATA_END                      "]]>"
+#define CDATA_END_LEN                  sizeof(CDATA_END) - 1
+#define EQUALS                         "="
+#define EQUALS_LEN                     sizeof(EQUALS) - 1
+#define WHITESPACE                     " "
+#define WHITESPACE_LEN                 sizeof(WHITESPACE) - 1
+#define NEWLINE                        "\n"
+#define NEWLINE_LEN                    sizeof(NEWLINE) - 1
+#define MAX_VAL_BUF                    144
+#define SCALAR_STR                     "SCALAR"
+#define SCALAR_STR_LEN                 sizeof(SCALAR_STR) - 1
+#define VECTOR_STR                     "VECTOR"
+#define VECTOR_STR_LEN                 sizeof(VECTOR_STR) - 1
+#define RESPONSE_STR                   "RESPONSE"
+#define RESPONSE_STR_LEN               sizeof(RESPONSE_STR) - 1
+
+
+/*-----------------------------
+- Begin xml_element Functions -
+-----------------------------*/
+
+/****f* xml_element/xml_elem_free_non_recurse
+ * NAME
+ *   xml_elem_free_non_recurse
+ * SYNOPSIS
+ *   void xml_elem_free_non_recurse(xml_element* root)
+ * FUNCTION
+ *   free a single xml element.  child elements will not be freed.
+ * INPUTS
+ *   root - the element to free
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_free ()
+ *   xml_elem_new ()
+ * SOURCE
+ */
+void xml_elem_free_non_recurse(xml_element* root) {
+   if(root) {
+      xml_element_attr* attrs = Q_Head(&root->attrs);
+      while(attrs) {
+         my_free(attrs->key);
+         my_free(attrs->val);
+         my_free(attrs);
+         attrs = Q_Next(&root->attrs);
+      }
+
+      Q_Destroy(&root->children);
+      Q_Destroy(&root->attrs);
+      my_free((char*)root->name);
+      simplestring_free(&root->text);
+      my_free(root);
+   }
+}
+/******/
+
+/****f* xml_element/xml_elem_free
+ * NAME
+ *   xml_elem_free
+ * SYNOPSIS
+ *   void xml_elem_free(xml_element* root)
+ * FUNCTION
+ *   free an xml element and all of its child elements
+ * INPUTS
+ *   root - the root of an xml tree you would like to free
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_free_non_recurse ()
+ *   xml_elem_new ()
+ * SOURCE
+ */
+void xml_elem_free(xml_element* root) {
+   if(root) {
+      xml_element* kids = Q_Head(&root->children);
+      while(kids) {
+         xml_elem_free(kids);
+         kids = Q_Next(&root->children);
+      }
+      xml_elem_free_non_recurse(root);
+   }
+}
+/******/
+
+/****f* xml_element/xml_elem_new
+ * NAME
+ *   xml_elem_new
+ * SYNOPSIS
+ *   xml_element* xml_elem_new()
+ * FUNCTION
+ *   allocates and initializes a new xml_element
+ * INPUTS
+ *   none
+ * RESULT
+ *   xml_element* or NULL.  NULL indicates an out-of-memory condition.
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_free ()
+ *   xml_elem_free_non_recurse ()
+ * SOURCE
+ */
+xml_element* xml_elem_new() {
+   xml_element* elem = calloc(1, sizeof(xml_element));
+   if(elem) {
+      Q_Init(&elem->children);
+      Q_Init(&elem->attrs);
+      simplestring_init(&elem->text);
+
+      /* init empty string in case we don't find any char data */
+      simplestring_addn(&elem->text, "", 0);
+   }
+   return elem;
+}
+/******/
+
+static int xml_elem_writefunc(int (*fptr)(void *data, const char *text, int size), const char *text, void *data, int len)
+{
+   return fptr && text ? fptr(data, text, len ? len : strlen(text)) : 0;
+}
+
+
+
+static int create_xml_escape(char *pString, unsigned char c)
+{ 
+  int counter = 0;
+
+  pString[counter++] = '&';
+  pString[counter++] = '#';
+  if(c >= 100) {
+    pString[counter++] = c / 100 + '0';
+    c = c % 100;
+  }
+  pString[counter++] = c / 10 + '0';
+  c = c % 10;
+
+  pString[counter++] = c + '0';
+  pString[counter++] = ';';
+  return counter; 
+}
+
+#define non_ascii(c) (c > 127)
+#define non_print(c) (!isprint(c))
+#define markup(c) (c == '&' || c == '\"' || c == '>' || c == '<')
+#define entity_length(c) ( (c >= 100) ? 3 : ((c >= 10) ? 2 : 1) ) + 3; /* "&#" + c + ";" */
+
+/*
+ * xml_elem_entity_escape
+ *
+ * Purpose:
+ *   escape reserved xml chars and non utf-8 chars as xml entities
+ * Comments:
+ *   The return value may be a new string, or null if no
+ *     conversion was performed. In the latter case, *newlen will
+ *     be 0.
+ * Flags (to escape)
+ *  xml_elem_no_escaping             = 0x000,
+ *  xml_elem_entity_escaping         = 0x002,   // escape xml special chars as entities
+ *  xml_elem_non_ascii_escaping      = 0x008,   // escape chars above 127
+ *  xml_elem_cdata_escaping          = 0x010,   // wrap in cdata
+ */
+static char* xml_elem_entity_escape(const char* buf, int old_len, int *newlen, XML_ELEM_ESCAPING flags) {
+  char *pRetval = 0;
+  int iNewBufLen=0;
+
+#define should_escape(c, flag) ( ((flag & xml_elem_markup_escaping) && markup(c)) || \
+                                 ((flag & xml_elem_non_ascii_escaping) && non_ascii(c)) || \
+                                 ((flag & xml_elem_non_print_escaping) && non_print(c)) )
+
+  if(buf && *buf) {
+    const unsigned char *bufcopy;
+    char *NewBuffer;
+    int ToBeXmlEscaped=0;
+    int iLength;
+    bufcopy = buf;
+    iLength= old_len ? old_len : strlen(buf);
+    while(*bufcopy) {
+      if( should_escape(*bufcopy, flags) ) {
+       /* the length will increase by length of xml escape - the character length */
+       iLength += entity_length(*bufcopy);
+       ToBeXmlEscaped=1;
+      }
+      bufcopy++;
+    }
+
+    if(ToBeXmlEscaped) {
+
+      NewBuffer= malloc(iLength+1);
+      if(NewBuffer) {
+       bufcopy=buf;
+       while(*bufcopy) {
+         if(should_escape(*bufcopy, flags)) {
+           iNewBufLen += create_xml_escape(NewBuffer+iNewBufLen,*bufcopy);
+         }
+         else {
+           NewBuffer[iNewBufLen++]=*bufcopy;
+         }
+         bufcopy++;
+       }
+       NewBuffer[iNewBufLen] = 0;
+       pRetval = NewBuffer;
+      }
+    }
+  }
+
+  if(newlen) {
+     *newlen = iNewBufLen;
+  }
+
+  return pRetval;
+}
+
+
+static void xml_element_serialize(xml_element *el, int (*fptr)(void *data, const char *text, int size), void *data, XML_ELEM_OUTPUT_OPTIONS options, int depth)
+{
+   int i;
+   static STRUCT_XML_ELEM_OUTPUT_OPTIONS default_opts = {xml_elem_pretty, xml_elem_markup_escaping | xml_elem_non_print_escaping, XML_DECL_ENCODING_DEFAULT};
+   static char whitespace[] = "                                                                                               "
+                              "                                                                                               "
+                              "                                                                                               ";
+   depth++;
+
+   if(!el) {
+      fprintf(stderr, "Nothing to write\n");
+      return;
+   }
+   if(!options) {
+      options = &default_opts;
+   }
+
+   /* print xml declaration if at root level */
+   if(depth == 1) {
+      xml_elem_writefunc(fptr, XML_DECL_START, data, XML_DECL_START_LEN);
+      xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN);
+      xml_elem_writefunc(fptr, XML_DECL_VERSION, data, XML_DECL_VERSION_LEN);
+      if(options->encoding && *options->encoding) {
+          xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN);
+          xml_elem_writefunc(fptr, XML_DECL_ENCODING_ATTR, data, XML_DECL_ENCODING_ATTR_LEN);
+          xml_elem_writefunc(fptr, EQUALS, data, EQUALS_LEN);
+          xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+          xml_elem_writefunc(fptr, options->encoding, data, 0);
+          xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+      }
+      xml_elem_writefunc(fptr, XML_DECL_END, data, XML_DECL_END_LEN);
+      if(options->verbosity != xml_elem_no_white_space) {
+         xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN);
+      }
+   }
+
+   if(options->verbosity == xml_elem_pretty && depth > 2) {
+         xml_elem_writefunc(fptr, whitespace, data, depth - 2);
+   }
+   /* begin element */
+   xml_elem_writefunc(fptr,START_TOKEN_BEGIN, data, START_TOKEN_BEGIN_LEN);
+   if(el->name) {
+      xml_elem_writefunc(fptr, el->name, data, 0);
+
+      /* write attrs, if any */
+      if(Q_Size(&el->attrs)) {
+         xml_element_attr* iter = Q_Head(&el->attrs);
+         while( iter ) {
+            xml_elem_writefunc(fptr, WHITESPACE, data, WHITESPACE_LEN);
+            xml_elem_writefunc(fptr, iter->key, data, 0);
+            xml_elem_writefunc(fptr, EQUALS, data, EQUALS_LEN);
+            xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+            xml_elem_writefunc(fptr, iter->val, data, 0);
+            xml_elem_writefunc(fptr, ATTR_DELIMITER, data, ATTR_DELIMITER_LEN);
+
+            iter = Q_Next(&el->attrs);
+         }
+      }
+   }
+   else {
+      xml_elem_writefunc(fptr, "None", data, 0);
+   }
+   /* if no text and no children, use abbreviated form, eg: <foo/> */
+   if(!el->text.len && !Q_Size(&el->children)) {
+       xml_elem_writefunc(fptr, EMPTY_START_TOKEN_END, data, EMPTY_START_TOKEN_END_LEN);
+   }
+   /* otherwise, print element contents */
+   else {
+       xml_elem_writefunc(fptr, START_TOKEN_END, data, START_TOKEN_END_LEN);
+
+       /* print text, if any */
+       if(el->text.len) {
+          char* escaped_str = el->text.str;
+          int buflen = el->text.len;
+
+          if(options->escaping && options->escaping != xml_elem_cdata_escaping) {
+             escaped_str = xml_elem_entity_escape(el->text.str, buflen, &buflen, options->escaping );
+             if(!escaped_str) {
+                escaped_str = el->text.str;
+             }
+          }
+
+          if(options->escaping & xml_elem_cdata_escaping) {
+             xml_elem_writefunc(fptr, CDATA_BEGIN, data, CDATA_BEGIN_LEN);
+          }
+
+          xml_elem_writefunc(fptr, escaped_str, data, buflen);
+
+          if(escaped_str != el->text.str) {
+             my_free(escaped_str);
+          }
+
+          if(options->escaping & xml_elem_cdata_escaping) {
+             xml_elem_writefunc(fptr, CDATA_END, data, CDATA_END_LEN);
+          }
+       }
+       /* no text, so print child elems */
+       else {
+          xml_element *kids = Q_Head(&el->children);
+          i = 0;
+          while( kids ) {
+             if(i++ == 0) {
+                if(options->verbosity != xml_elem_no_white_space) {
+                   xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN);
+                }
+             }
+             xml_element_serialize(kids, fptr, data, options, depth);
+             kids = Q_Next(&el->children);
+          }
+          if(i) {
+             if(options->verbosity == xml_elem_pretty && depth > 2) {
+                   xml_elem_writefunc(fptr, whitespace, data, depth - 2);
+             }
+          }
+       }
+
+       xml_elem_writefunc(fptr, END_TOKEN_BEGIN, data, END_TOKEN_BEGIN_LEN);
+       xml_elem_writefunc(fptr,el->name ? el->name : "None", data, 0);
+       xml_elem_writefunc(fptr, END_TOKEN_END, data, END_TOKEN_END_LEN);
+   }
+   if(options->verbosity != xml_elem_no_white_space) {
+      xml_elem_writefunc(fptr, NEWLINE, data, NEWLINE_LEN);
+   }
+}
+
+/* print buf to file */
+static int file_out_fptr(void *f, const char *text, int size)
+{
+   fputs(text, (FILE *)f);
+   return 0;
+}
+
+/* print buf to simplestring */
+static int simplestring_out_fptr(void *f, const char *text, int size)
+{
+   simplestring* buf = (simplestring*)f;
+   if(buf) {
+      simplestring_addn(buf, text, size);
+   }
+   return 0;
+}
+
+/****f* xml_element/xml_elem_serialize_to_string
+ * NAME
+ *   xml_elem_serialize_to_string
+ * SYNOPSIS
+ *   void xml_element_serialize_to_string(xml_element *el, XML_ELEM_OUTPUT_OPTIONS options, int *buf_len)
+ * FUNCTION
+ *   writes element tree as XML into a newly allocated buffer
+ * INPUTS
+ *   el      - root element of tree
+ *   options - options determining how output is written.  see XML_ELEM_OUTPUT_OPTIONS
+ *   buf_len - length of returned buffer, if not null.
+ * RESULT
+ *   char* or NULL. Must be free'd by caller.
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_serialize_to_stream ()
+ *   xml_elem_parse_buf ()
+ * SOURCE
+ */
+char* xml_elem_serialize_to_string(xml_element *el, XML_ELEM_OUTPUT_OPTIONS options, int *buf_len)
+{
+   simplestring buf;
+   simplestring_init(&buf);
+
+   xml_element_serialize(el, simplestring_out_fptr, (void *)&buf, options, 0);
+
+   if(buf_len) {
+      *buf_len = buf.len;
+   }
+
+   return buf.str;
+}
+/******/
+
+/****f* xml_element/xml_elem_serialize_to_stream
+ * NAME
+ *   xml_elem_serialize_to_stream
+ * SYNOPSIS
+ *   void xml_elem_serialize_to_stream(xml_element *el, FILE *output, XML_ELEM_OUTPUT_OPTIONS options)
+ * FUNCTION
+ *   writes element tree as XML into a stream (typically an opened file)
+ * INPUTS
+ *   el      - root element of tree
+ *   output  - stream handle
+ *   options - options determining how output is written.  see XML_ELEM_OUTPUT_OPTIONS
+ * RESULT
+ *   void
+ * NOTES
+ * SEE ALSO
+ *   xml_elem_serialize_to_string ()
+ *   xml_elem_parse_buf ()
+ * SOURCE
+ */
+void xml_elem_serialize_to_stream(xml_element *el, FILE *output, XML_ELEM_OUTPUT_OPTIONS options)
+{
+   xml_element_serialize(el, file_out_fptr, (void *)output, options, 0);
+}
+/******/
+
+/*--------------------------*
+* End xml_element Functions *
+*--------------------------*/
+
+
+/*----------------------
+* Begin Expat Handlers *
+*---------------------*/
+
+typedef struct _xml_elem_data {
+   xml_element*           root;
+   xml_element*           current;
+   XML_ELEM_INPUT_OPTIONS input_options;
+   int                    needs_enc_conversion;
+} xml_elem_data;
+
+
+/* expat start of element handler */
+static void startElement(void *userData, const char *name, const char **attrs)
+{
+   xml_element *c;
+   xml_elem_data* mydata = (xml_elem_data*)userData;
+   const char** p = attrs;
+
+   if(mydata) {
+      c = mydata->current;
+
+      mydata->current = xml_elem_new();
+      mydata->current->name = (char*)strdup(name);
+      mydata->current->parent = c;
+
+      /* init attrs */
+      while(p && *p) {
+         xml_element_attr* attr = malloc(sizeof(xml_element_attr));
+         if(attr) {
+            attr->key = strdup(*p);
+            attr->val = strdup(*(p+1));
+            Q_PushTail(&mydata->current->attrs, attr);
+
+            p += 2;
+         }
+      }
+   }
+}
+
+/* expat end of element handler */
+static void endElement(void *userData, const char *name)
+{
+   xml_elem_data* mydata = (xml_elem_data*)userData;
+
+   if(mydata && mydata->current && mydata->current->parent) {
+      Q_PushTail(&mydata->current->parent->children, mydata->current);
+
+      mydata->current = mydata->current->parent;
+   }
+}
+
+/* expat char data handler */
+static void charHandler(void *userData,
+                        const char *s,
+                        int len)
+{
+   xml_elem_data* mydata = (xml_elem_data*)userData;
+   if(mydata && mydata->current) {
+
+      /* Check if we need to decode utf-8 parser output to another encoding */
+      if(mydata->needs_enc_conversion && mydata->input_options->encoding) {
+         int new_len = 0;
+         char* add_text = utf8_decode(s, len, &new_len, mydata->input_options->encoding);
+         if(add_text) {
+            len = new_len;
+            simplestring_addn(&mydata->current->text, add_text, len);
+            free(add_text);
+            return;
+         }
+      }
+      simplestring_addn(&mydata->current->text, s, len);
+   }
+}
+/******/
+
+/*-------------------*
+* End Expat Handlers *
+*-------------------*/
+
+/*-------------------*
+* xml_elem_parse_buf *
+*-------------------*/
+
+/****f* xml_element/xml_elem_parse_buf
+ * NAME
+ *   xml_elem_parse_buf
+ * SYNOPSIS
+ *   xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTIONS options, XML_ELEM_ERROR error)
+ * FUNCTION
+ *   parse a buffer containing XML into an xml_element in-memory tree
+ * INPUTS
+ *   in_buf   - buffer containing XML document
+ *   len      - length of buffer
+ *   options  - input options. optional
+ *   error    - error result data. optional. check if result is null.
+ * RESULT
+ *   void
+ * NOTES
+ *   The returned data must be free'd by caller
+ * SEE ALSO
+ *   xml_elem_serialize_to_string ()
+ *   xml_elem_free ()
+ * SOURCE
+ */
+xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTIONS options, XML_ELEM_ERROR error)
+{
+   xml_element* xReturn = NULL;
+   char buf[100] = "";
+   static STRUCT_XML_ELEM_INPUT_OPTIONS default_opts = {encoding_utf_8};
+
+   if(!options) {
+      options = &default_opts;
+   }
+
+   if(in_buf) {
+      XML_Parser parser;
+      xml_elem_data mydata = {0};
+
+      parser = XML_ParserCreate(NULL);
+
+      mydata.root = xml_elem_new();
+      mydata.current = mydata.root;
+      mydata.input_options = options;
+      mydata.needs_enc_conversion = options->encoding && strcmp(options->encoding, encoding_utf_8);
+
+      XML_SetElementHandler(parser, startElement, endElement);
+      XML_SetCharacterDataHandler(parser, charHandler);
+
+      /* pass the xml_elem_data struct along */
+      XML_SetUserData(parser, (void*)&mydata);
+
+      if(!len) {
+         len = strlen(in_buf);
+      }
+
+      /* parse the XML */
+      if(XML_Parse(parser, in_buf, len, 1) == 0) {
+         enum XML_Error err_code = XML_GetErrorCode(parser);
+         int line_num = XML_GetCurrentLineNumber(parser);
+         int col_num = XML_GetCurrentColumnNumber(parser);
+         long byte_idx = XML_GetCurrentByteIndex(parser);
+         int byte_total = XML_GetCurrentByteCount(parser);
+         const char * error_str = XML_ErrorString(err_code);
+         if(byte_idx >= 0) {
+             snprintf(buf, 
+                      sizeof(buf),
+                      "\n\tdata beginning %ld before byte index: %s\n",
+                      byte_idx > 10  ? 10 : byte_idx,
+                      in_buf + (byte_idx > 10 ? byte_idx - 10 : byte_idx));
+         }
+
+         fprintf(stderr, "expat reports error code %i\n"
+                "\tdescription: %s\n"
+                "\tline: %i\n"
+                "\tcolumn: %i\n"
+                "\tbyte index: %ld\n"
+                "\ttotal bytes: %i\n%s ",
+                err_code, error_str, line_num, 
+                col_num, byte_idx, byte_total, buf);
+
+
+          /* error condition */
+          if(error) {
+              error->parser_code = (long)err_code;
+              error->line = line_num;
+              error->column = col_num;
+              error->byte_index = byte_idx;
+              error->parser_error = error_str;
+          }
+      }
+      else {
+         xReturn = (xml_element*)Q_Head(&mydata.root->children);
+         xReturn->parent = NULL;
+      }
+
+      XML_ParserFree(parser);
+
+
+      xml_elem_free_non_recurse(mydata.root);
+   }
+
+   return xReturn;
+}
+
+/******/
diff --git a/php/xmlrpc/libxmlrpc/xml_element.h b/php/xmlrpc/libxmlrpc/xml_element.h
new file mode 100644 (file)
index 0000000..cfe7ca2
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+#ifndef __XML_ELEMENT_H__
+ #define __XML_ELEMENT_H__
+
+/* includes */
+#include <stdio.h>
+#include "queue.h"
+#include "simplestring.h"
+#include "encodings.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/****d* enum/XML_ELEM_VERBOSITY
+ * NAME
+ *   XML_ELEM_VERBOSITY
+ * NOTES
+ *   verbosity/readability options for generated xml
+ * SEE ALSO
+ *   XML_ELEM_OUTPUT_OPTIONS
+ * SOURCE
+ */
+typedef enum _xml_elem_verbosity {
+   xml_elem_no_white_space,    /* compact xml with no white space            */
+   xml_elem_newlines_only,     /* add newlines for enhanced readability      */
+   xml_elem_pretty             /* add newlines and indent accordint to depth */
+} XML_ELEM_VERBOSITY;
+/******/
+
+
+/****d* enum/XML_ELEM_ESCAPING
+ * NAME
+ *   XML_ELEM_ESCAPING
+ * NOTES
+ * xml escaping options for generated xml
+ * SEE ALSO
+ *   XML_ELEM_OUTPUT_OPTIONS
+ * SOURCE
+ */
+typedef enum _xml_elem_escaping {
+   xml_elem_no_escaping             = 0x000,
+   xml_elem_markup_escaping         = 0x002,   /* entity escape xml special chars         */
+   xml_elem_non_ascii_escaping      = 0x008,   /* entity escape chars above 127           */
+   xml_elem_non_print_escaping      = 0x010,   /* entity escape non print (illegal) chars */
+   xml_elem_cdata_escaping          = 0x020,   /* wrap in cdata section                   */
+} XML_ELEM_ESCAPING;
+/******/
+
+
+/****s* struct/XML_ELEM_OUTPUT_OPTIONS
+ * NAME
+ *   XML_ELEM_OUTPUT_OPTIONS
+ * NOTES
+ *   defines various output options
+ * SOURCE
+ */
+typedef struct _xml_output_options {
+   XML_ELEM_VERBOSITY           verbosity;      /* length/verbosity of xml        */
+   XML_ELEM_ESCAPING            escaping;       /* how to escape special chars    */
+   const char*                  encoding;       /* <?xml encoding="<encoding>" ?> */
+} STRUCT_XML_ELEM_OUTPUT_OPTIONS, *XML_ELEM_OUTPUT_OPTIONS;
+/******/
+
+/****s* struct/XML_ELEM_INPUT_OPTIONS
+ * NAME
+ *   XML_ELEM_INPUT_OPTIONS
+ * NOTES
+ *   defines various input options
+ * SOURCE
+ */
+typedef struct _xml_input_options {
+  ENCODING_ID                  encoding;       /* which encoding to use.       */
+} STRUCT_XML_ELEM_INPUT_OPTIONS, *XML_ELEM_INPUT_OPTIONS;
+/******/
+
+/****s* struct/XML_ELEM_ERROR
+ * NAME
+ *   XML_ELEM_ERROR
+ * NOTES
+ *   defines an xml parser error
+ * SOURCE
+ */
+typedef struct _xml_elem_error {
+  int parser_code;
+  const char* parser_error;
+  long line;
+  long column;
+  long byte_index;
+} STRUCT_XML_ELEM_ERROR, *XML_ELEM_ERROR;
+/******/
+
+
+/*-************************
+* begin xml element stuff *
+**************************/
+
+/****s* struct/xml_elem_attr
+ * NAME
+ *  xml_elem_attr
+ * NOTES
+ *   representation of an xml attribute, foo="bar"
+ * SOURCE
+ */
+typedef struct _xml_element_attr {
+   char* key;        /* attribute key   */
+   char* val;        /* attribute value */
+} xml_element_attr;
+/******/
+
+/****s* struct/xml_elem_attr
+ * NAME
+ *  xml_elem_attr
+ * NOTES
+ *   representation of an xml element, eg <candidate name="Harry Browne" party="Libertarian"/>
+ * SOURCE
+ */
+typedef struct _xml_element {
+   const char*   name;           /* element identifier */
+   simplestring  text;           /* text contained between element begin/end pairs */
+   struct _xml_element* parent;  /* element's parent */
+                                 
+   queue        attrs;           /* attribute list */
+   queue        children;        /* child element list */
+} xml_element;
+/******/
+
+void xml_elem_free(xml_element* root);
+void xml_elem_free_non_recurse(xml_element* root);
+xml_element* xml_elem_new(void);
+char* xml_elem_serialize_to_string(xml_element *el, XML_ELEM_OUTPUT_OPTIONS options, int *buf_len);
+void xml_elem_serialize_to_stream(xml_element *el, FILE *output, XML_ELEM_OUTPUT_OPTIONS options);
+xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTIONS options, XML_ELEM_ERROR error);
+
+/*-**********************
+* end xml element stuff *
+************************/
+
+/*-**********************
+* Begin xml_element API *
+************************/
+
+/****d* VALUE/XMLRPC_MACROS
+ * NAME
+ *   Some Helpful Macros
+ * NOTES
+ *   Some macros for making life easier.  Should be self-explanatory.
+ * SEE ALSO
+ *   XMLRPC_AddValueToVector ()
+ *   XMLRPC_VectorGetValueWithID_Case ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+#define xml_elem_next_element(el) ((el) ? (xml_element *)Q_Next(&el->children) : NULL)
+#define xml_elem_head_element(el) ((el) ? (xml_element *)Q_Head(&el->children) : NULL)
+#define xml_elem_next_attr(el) ((el) ? (xml_element_attr *)Q_Next(&el->attrs) : NULL)
+#define xml_elem_head_attr(el) ((el) ? (xml_element_attr *)Q_Head(&el->attrs) : NULL)
+#define xml_elem_get_name(el) (char *)((el) ? el->name : NULL)
+#define xml_elem_get_val(el) (char *)((el) ? el->text.str : NULL)
+/******/
+
+
+/*-********************
+* End xml_element API *
+**********************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __XML_ELEMENT_H__ */
diff --git a/php/xmlrpc/libxmlrpc/xml_to_dandarpc.c b/php/xmlrpc/libxmlrpc/xml_to_dandarpc.c
new file mode 100644 (file)
index 0000000..b51d991
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include "xml_to_dandarpc.h"
+#include "base64.h"
+
+/* list of tokens used in vocab */
+#define ELEM_METHODCALL     "methodCall"
+#define ELEM_METHODNAME     "methodName"
+#define ELEM_METHODRESPONSE "methodResponse"
+#define ELEM_ROOT           "simpleRPC"
+
+#define ATTR_ARRAY          "array"
+#define ATTR_BASE64         "base64"
+#define ATTR_BOOLEAN        "boolean"
+#define ATTR_DATETIME       "dateTime.iso8601"
+#define ATTR_DOUBLE         "double"
+#define ATTR_ID             "id"
+#define ATTR_INT            "int"
+#define ATTR_MIXED          "mixed"
+#define ATTR_SCALAR         "scalar"
+#define ATTR_STRING         "string"
+#define ATTR_STRUCT         "struct"
+#define ATTR_TYPE           "type"
+#define ATTR_VECTOR         "vector"
+#define ATTR_VERSION        "version"
+
+#define VAL_VERSION_0_9     "0.9"
+
+
+XMLRPC_VALUE xml_element_to_DANDARPC_REQUEST_worker(XMLRPC_REQUEST request, XMLRPC_VALUE xCurrent, xml_element* el) {
+   if(!xCurrent) {
+      xCurrent = XMLRPC_CreateValueEmpty();
+   }
+
+   if(el->name) {
+      const char* id = NULL;
+      const char* type = NULL;
+      xml_element_attr* attr_iter = Q_Head(&el->attrs);
+
+      while(attr_iter) {
+         if(!strcmp(attr_iter->key, ATTR_ID)) {
+            id = attr_iter->val;
+         }
+         if(!strcmp(attr_iter->key, ATTR_TYPE)) {
+            type = attr_iter->val;
+         }
+         attr_iter = Q_Next(&el->attrs);
+      }
+
+      if(id) {
+         XMLRPC_SetValueID_Case(xCurrent, id, 0, xmlrpc_case_exact);
+      }
+
+      if(!strcmp(el->name, ATTR_SCALAR)) {
+         if(!type || !strcmp(type, ATTR_STRING)) {
+            XMLRPC_SetValueString(xCurrent, el->text.str, el->text.len);
+         }
+         else if(!strcmp(type, ATTR_INT)) {
+            XMLRPC_SetValueInt(xCurrent, atoi(el->text.str));
+         }
+         else if(!strcmp(type, ATTR_BOOLEAN)) {
+            XMLRPC_SetValueBoolean(xCurrent, atoi(el->text.str));
+         }
+         else if(!strcmp(type, ATTR_DOUBLE)) {
+            XMLRPC_SetValueDouble(xCurrent, atof(el->text.str));
+         }
+         else if(!strcmp(type, ATTR_DATETIME)) {
+            XMLRPC_SetValueDateTime_ISO8601(xCurrent, el->text.str);
+         }
+         else if(!strcmp(type, ATTR_BASE64)) {
+            struct buffer_st buf;
+            base64_decode(&buf, el->text.str, el->text.len);
+            XMLRPC_SetValueBase64(xCurrent, buf.data, buf.offset);
+            buffer_delete(&buf);
+         }
+      }
+      else if(!strcmp(el->name, ATTR_VECTOR)) {
+         xml_element* iter = (xml_element*)Q_Head(&el->children);
+
+         if(!type || !strcmp(type, ATTR_MIXED)) {
+            XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_mixed);
+         }
+         else if(!strcmp(type, ATTR_ARRAY)) {
+                               XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_array);
+         }
+         else if(!strcmp(type, ATTR_STRUCT)) {
+            XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_struct);
+         }
+         while( iter ) {
+            XMLRPC_VALUE xNext = XMLRPC_CreateValueEmpty();
+            xml_element_to_DANDARPC_REQUEST_worker(request, xNext, iter);
+            XMLRPC_AddValueToVector(xCurrent, xNext);
+            iter = (xml_element*)Q_Next(&el->children);
+         }
+      }
+      else {
+         xml_element* iter = (xml_element*)Q_Head(&el->children);
+         while( iter ) {
+            xml_element_to_DANDARPC_REQUEST_worker(request, xCurrent, iter);
+            iter = (xml_element*)Q_Next(&el->children);
+         }
+
+         if(!strcmp(el->name, ELEM_METHODCALL)) {
+            if(request) {
+               XMLRPC_RequestSetRequestType(request, xmlrpc_request_call);
+            }
+         }
+         else if(!strcmp(el->name, ELEM_METHODRESPONSE)) {
+            if(request) {
+               XMLRPC_RequestSetRequestType(request, xmlrpc_request_response);
+            }
+         }
+         else if(!strcmp(el->name, ELEM_METHODNAME)) {
+            if(request) {
+               XMLRPC_RequestSetMethodName(request, el->text.str);
+            }
+         }
+      }
+   }
+   return xCurrent;
+}
+
+XMLRPC_VALUE xml_element_to_DANDARPC_VALUE(xml_element* el)
+{
+   return xml_element_to_DANDARPC_REQUEST_worker(NULL, NULL, el);
+}
+
+XMLRPC_VALUE xml_element_to_DANDARPC_REQUEST(XMLRPC_REQUEST request, xml_element* el)
+{
+   if(request) {
+      return XMLRPC_RequestSetData(request, xml_element_to_DANDARPC_REQUEST_worker(request, NULL, el));
+   }
+   return NULL;
+}
+
+xml_element* DANDARPC_to_xml_element_worker(XMLRPC_REQUEST request, XMLRPC_VALUE node) {
+#define BUF_SIZE 512
+   xml_element* root = NULL;
+   if(node) {
+      char buf[BUF_SIZE];
+      const char* id = XMLRPC_GetValueID(node);
+      XMLRPC_VALUE_TYPE type = XMLRPC_GetValueType(node);
+      XMLRPC_REQUEST_OUTPUT_OPTIONS output = XMLRPC_RequestGetOutputOptions(request);
+      int bNoAddType = (type == xmlrpc_string && request && output && output->xml_elem_opts.verbosity == xml_elem_no_white_space);
+      xml_element* elem_val = xml_elem_new();
+      const char* pAttrType = NULL;
+
+      xml_element_attr* attr_type = bNoAddType ? NULL : malloc(sizeof(xml_element_attr));
+       
+      if(attr_type) {
+         attr_type->key = strdup(ATTR_TYPE);
+         attr_type->val = 0;
+         Q_PushTail(&elem_val->attrs, attr_type);
+      }
+
+      elem_val->name = (type == xmlrpc_vector) ? strdup(ATTR_VECTOR) : strdup(ATTR_SCALAR);
+
+      if(id && *id) {
+         xml_element_attr* attr_id = malloc(sizeof(xml_element_attr));
+         if(attr_id) {
+            attr_id->key = strdup(ATTR_ID);
+            attr_id->val = strdup(id);
+            Q_PushTail(&elem_val->attrs, attr_id);
+         }
+      }
+
+      switch(type) {
+         case xmlrpc_string:
+            pAttrType = ATTR_STRING;
+            simplestring_addn(&elem_val->text, XMLRPC_GetValueString(node), XMLRPC_GetValueStringLen(node));
+            break;
+         case xmlrpc_int:
+            pAttrType = ATTR_INT;
+            snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueInt(node));
+            simplestring_add(&elem_val->text, buf);
+            break;
+         case xmlrpc_boolean:
+            pAttrType = ATTR_BOOLEAN;
+            snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueBoolean(node));
+            simplestring_add(&elem_val->text, buf);
+            break;
+         case xmlrpc_double:
+            pAttrType = ATTR_DOUBLE;
+            snprintf(buf, BUF_SIZE, "%f", XMLRPC_GetValueDouble(node));
+            simplestring_add(&elem_val->text, buf);
+            break;
+         case xmlrpc_datetime:
+            pAttrType = ATTR_DATETIME;
+            simplestring_add(&elem_val->text, XMLRPC_GetValueDateTime_ISO8601(node));
+            break;
+         case xmlrpc_base64:
+            {
+               struct buffer_st buf;
+               pAttrType = ATTR_BASE64;
+               base64_encode(&buf, XMLRPC_GetValueBase64(node), XMLRPC_GetValueStringLen(node));
+               simplestring_addn(&elem_val->text, buf.data, buf.offset );
+               buffer_delete(&buf);
+            }
+            break;
+         case xmlrpc_vector:
+            {
+               XMLRPC_VECTOR_TYPE my_type = XMLRPC_GetVectorType(node);
+               XMLRPC_VALUE xIter = XMLRPC_VectorRewind(node);
+
+               switch(my_type) {
+                  case xmlrpc_vector_array:
+                     pAttrType = ATTR_ARRAY;
+                     break;
+                  case xmlrpc_vector_mixed:
+                     pAttrType = ATTR_MIXED;
+                     break;
+                  case xmlrpc_vector_struct:
+                     pAttrType = ATTR_STRUCT;
+                     break;
+                  default:
+                     break;
+               }
+
+               /* recurse through sub-elements */
+               while( xIter ) {
+                  xml_element* next_el = DANDARPC_to_xml_element_worker(request, xIter);
+                  if(next_el) {
+                     Q_PushTail(&elem_val->children, next_el);
+                  }
+                  xIter = XMLRPC_VectorNext(node);
+               }
+            }
+            break;
+         default:
+            break;
+      }
+      if(pAttrType && attr_type && !bNoAddType) {
+         attr_type->val = strdup(pAttrType);
+      }
+      root = elem_val;
+   }
+   return root;
+}
+
+xml_element* DANDARPC_VALUE_to_xml_element(XMLRPC_VALUE node) {
+   return DANDARPC_to_xml_element_worker(NULL, node);
+}
+
+xml_element* DANDARPC_REQUEST_to_xml_element(XMLRPC_REQUEST request) {
+   xml_element* wrapper = NULL;
+   xml_element* root = NULL;
+   if(request) {
+      XMLRPC_REQUEST_TYPE request_type = XMLRPC_RequestGetRequestType(request);
+      const char* pStr = NULL;
+      xml_element_attr* version = malloc(sizeof(xml_element_attr));
+      version->key = strdup(ATTR_VERSION);
+      version->val = strdup(VAL_VERSION_0_9);
+      
+      wrapper = xml_elem_new();
+
+      if(request_type == xmlrpc_request_response) {
+         pStr = ELEM_METHODRESPONSE;
+      }
+      else if(request_type == xmlrpc_request_call) {
+         pStr = ELEM_METHODCALL;
+      }
+      if(pStr) {
+         wrapper->name = strdup(pStr);
+      }
+
+      root = xml_elem_new();
+      root->name = strdup(ELEM_ROOT);
+      Q_PushTail(&root->attrs, version);
+      Q_PushTail(&root->children, wrapper);
+
+      pStr = XMLRPC_RequestGetMethodName(request);
+
+      if(pStr) {
+         xml_element* method = xml_elem_new();
+         method->name = strdup(ELEM_METHODNAME);
+         simplestring_add(&method->text, pStr);
+         Q_PushTail(&wrapper->children, method);
+      }
+      Q_PushTail(&wrapper->children, 
+                 DANDARPC_to_xml_element_worker(request, XMLRPC_RequestGetData(request)));
+   }
+   return root;
+}
+
diff --git a/php/xmlrpc/libxmlrpc/xml_to_dandarpc.h b/php/xmlrpc/libxmlrpc/xml_to_dandarpc.h
new file mode 100644 (file)
index 0000000..6facb55
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+#ifndef XML_TO_DANDARPC_H
+ #define XML_TO_DANDARPC_H
+
+#include "time.h" 
+#include "xmlrpc.h"
+
+XMLRPC_VALUE xml_element_to_DANDARPC_VALUE(xml_element* el);
+XMLRPC_VALUE xml_element_to_DANDARPC_REQUEST(XMLRPC_REQUEST request, xml_element* el);
+xml_element* DANDARPC_VALUE_to_xml_element(XMLRPC_VALUE node);
+xml_element* DANDARPC_REQUEST_to_xml_element(XMLRPC_REQUEST request);
+
+#endif /* XML_TO_DANDARPC_H */
diff --git a/php/xmlrpc/libxmlrpc/xml_to_soap.c b/php/xmlrpc/libxmlrpc/xml_to_soap.c
new file mode 100644 (file)
index 0000000..8390f06
--- /dev/null
@@ -0,0 +1,670 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+*/
+
+
+/*-**********************************************************************
+* TODO:                                                                 *
+*  - [SOAP-ENC:position] read sparse arrays (and write?)                *
+*  - [SOAP-ENC:offset] read partially transmitted arrays  (and write?)  *
+*  - read "flattened" multi-dimensional arrays. (don't bother writing)  *
+*                                                                       *
+* BUGS:                                                                 *
+*  - does not read schema. thus only knows soap pre-defined types.      *
+*  - references (probably) do not work. untested.                       *
+*  - does not expose SOAP-ENV:Header to application at all.             *
+*  - does not use namespaces correctly, thus:                           *
+*    - namespaces are hard-coded in comparison tokens                   *
+*    - if a sender uses another namespace identifer, it will break      *
+************************************************************************/
+
+
+static const char rcsid[] = "#(@) $Id:";
+
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include "xml_to_soap.h"
+#include "base64.h"
+
+/* list of tokens used in vocab */
+#define TOKEN_ANY                               "xsd:ur-type"
+#define TOKEN_ARRAY          "SOAP-ENC:Array"
+#define TOKEN_ARRAY_TYPE     "SOAP-ENC:arrayType"
+#define TOKEN_BASE64         "SOAP-ENC:base64"
+#define TOKEN_BOOLEAN        "xsd:boolean"
+#define TOKEN_DATETIME       "xsd:timeInstant"
+#define TOKEN_DOUBLE         "xsd:double"
+#define TOKEN_FLOAT          "xsd:float"
+#define TOKEN_ID             "id"
+#define TOKEN_INT            "xsd:int"
+#define TOKEN_NULL           "xsi:null"
+#define TOKEN_STRING         "xsd:string"
+#define TOKEN_STRUCT                    "xsd:struct"
+#define TOKEN_TYPE           "xsi:type"
+#define TOKEN_FAULT                     "SOAP-ENV:Fault"
+#define TOKEN_MUSTUNDERSTAND "SOAP-ENV:mustUnderstand"
+#define TOKEN_ACTOR                     "SOAP-ENV:actor"
+#define TOKEN_ACTOR_NEXT                "http://schemas.xmlsoap.org/soap/actor/next"
+
+#define TOKEN_XMLRPC_FAULTCODE   "faultCode"
+#define TOKEN_XMLRPC_FAULTSTRING "faultString"
+#define TOKEN_SOAP_FAULTCODE     "faultcode"
+#define TOKEN_SOAP_FAULTSTRING   "faultstring"
+#define TOKEN_SOAP_FAULTDETAILS  "details"
+#define TOKEN_SOAP_FAULTACTOR    "actor"
+
+
+/* determine if a string represents a soap type, as used in element names */
+static inline int is_soap_type(const char* soap_type) {
+       return(strstr(soap_type, "SOAP-ENC:") || strstr(soap_type, "xsd:")) ? 1 : 0;
+}
+
+/* utility func to generate a new attribute. possibly should be in xml_element.c?? */
+static xml_element_attr* new_attr(const char* key, const char* val) {
+       xml_element_attr* attr = malloc(sizeof(xml_element_attr));
+       if (attr) {
+               attr->key = key ? strdup(key) : NULL;
+               attr->val = val ? strdup(val) : NULL;
+       }
+       return attr;
+}
+
+struct array_info {
+       char          kids_type[30];
+       unsigned long size;
+       /* ... ? */
+};
+
+
+/* parses soap arrayType attribute to generate an array_info structure.
+ * TODO: should deal with sparse, flattened, & multi-dimensional arrays
+ */
+static struct array_info* parse_array_type_info(const char* array_type) {
+       struct array_info* ai = NULL;
+       if (array_type) {
+               ai = (struct array_info*)calloc(1, sizeof(struct array_info));
+               if (ai) {
+                       char buf[128], *p;
+                       snprintf(buf, sizeof(buf), "%s", array_type);
+                       p = strchr(buf, '[');
+                       if (p) {
+                               *p = 0;
+                       }
+                       strcpy(ai->kids_type, buf);
+               }
+       }
+       return ai;
+}
+
+/* performs heuristics on an xmlrpc_vector_array to determine
+ * appropriate soap arrayType string.
+ */
+static const char* get_array_soap_type(XMLRPC_VALUE node) {
+       XMLRPC_VALUE_TYPE_EASY type = xmlrpc_type_none;
+
+       XMLRPC_VALUE xIter = XMLRPC_VectorRewind(node);
+       int loopCount = 0;
+       const char* soapType = TOKEN_ANY;
+
+       type = XMLRPC_GetValueTypeEasy(xIter);
+       xIter = XMLRPC_VectorNext(node);
+
+       while (xIter) {
+               /* 50 seems like a decent # of loops.  That will likely
+                * cover most cases.  Any more and we start to sacrifice
+                * performance.
+                */
+               if ( (XMLRPC_GetValueTypeEasy(xIter) != type) || loopCount >= 50) {
+                       type = xmlrpc_type_none;
+                       break;
+               }
+               loopCount ++;
+
+               xIter = XMLRPC_VectorNext(node);
+       }
+       switch (type) {
+       case xmlrpc_type_none:
+               soapType = TOKEN_ANY;
+               break;
+       case xmlrpc_type_empty:
+               soapType = TOKEN_NULL;
+               break;
+       case xmlrpc_type_int:
+               soapType = TOKEN_INT;
+               break;
+       case xmlrpc_type_double:
+               soapType = TOKEN_DOUBLE;
+               break;
+       case xmlrpc_type_boolean:
+               soapType = TOKEN_BOOLEAN;
+               break;
+       case xmlrpc_type_string:
+               soapType = TOKEN_STRING;
+               break;
+       case xmlrpc_type_base64:
+               soapType = TOKEN_BASE64;
+               break;
+       case xmlrpc_type_datetime:
+               soapType = TOKEN_DATETIME;
+               break;
+       case xmlrpc_type_struct:
+               soapType = TOKEN_STRUCT;
+               break;
+       case xmlrpc_type_array:
+               soapType = TOKEN_ARRAY;
+               break;
+       case xmlrpc_type_mixed:
+               soapType = TOKEN_STRUCT;
+               break;
+       }
+       return soapType;
+}
+
+/* determines wether a node is a fault or not, and of which type:
+ * 0 = not a fault,
+ * 1 = xmlrpc style fault
+ * 2 = soap style fault.
+ */
+static inline int get_fault_type(XMLRPC_VALUE node) {
+       if (XMLRPC_VectorGetValueWithID(node, TOKEN_XMLRPC_FAULTCODE) &&
+                XMLRPC_VectorGetValueWithID(node, TOKEN_XMLRPC_FAULTSTRING)) {
+               return 1;
+       }
+       else if (XMLRPC_VectorGetValueWithID(node, TOKEN_SOAP_FAULTCODE) &&
+                               XMLRPC_VectorGetValueWithID(node, TOKEN_SOAP_FAULTSTRING)) {
+               return 2;
+       }
+       return 0;
+}
+
+/* input: an XMLRPC_VALUE representing a fault struct in xml-rpc style.
+ * output: an XMLRPC_VALUE representing a fault struct in soap style,
+ *  with xmlrpc codes mapped to soap codes, and all other values preserved.
+ *  note that the returned value is a completely new value, and must be freed.
+ *  the input value is untouched.
+ */
+static XMLRPC_VALUE gen_fault_xmlrpc(XMLRPC_VALUE node, xml_element* el_target) {
+       XMLRPC_VALUE xDup = XMLRPC_DupValueNew(node);
+       XMLRPC_VALUE xCode = XMLRPC_VectorGetValueWithID(xDup, TOKEN_XMLRPC_FAULTCODE);
+       XMLRPC_VALUE xStr = XMLRPC_VectorGetValueWithID(xDup, TOKEN_XMLRPC_FAULTSTRING);
+
+       XMLRPC_SetValueID(xCode, TOKEN_SOAP_FAULTCODE, 0);
+       XMLRPC_SetValueID(xStr, TOKEN_SOAP_FAULTSTRING, 0);
+
+       /* rough mapping of xmlrpc fault codes to soap codes */
+       switch (XMLRPC_GetValueInt(xCode)) {
+       case -32700:              /* "parse error. not well formed", */
+       case -32701:              /* "parse error. unsupported encoding" */
+       case -32702:              /* "parse error. invalid character for encoding" */
+       case -32600:              /* "server error. invalid xml-rpc.  not conforming to spec." */
+       case -32601:              /* "server error. requested method not found" */
+       case -32602:              /* "server error. invalid method parameters" */
+               XMLRPC_SetValueString(xCode, "SOAP-ENV:Client", 0);
+               break;
+       case -32603:              /* "server error. internal xml-rpc error" */
+       case -32500:              /* "application error" */
+       case -32400:              /* "system error" */
+       case -32300:              /* "transport error */
+               XMLRPC_SetValueString(xCode, "SOAP-ENV:Server", 0);
+               break;
+       }
+       return xDup;
+}
+
+/* returns a new XMLRPC_VALUE representing a soap fault, comprised of a struct with four keys. */
+static XMLRPC_VALUE gen_soap_fault(const char* fault_code, const char* fault_string, 
+                                                                                         const char* actor, const char* details) {
+       XMLRPC_VALUE xReturn = XMLRPC_CreateVector(TOKEN_FAULT, xmlrpc_vector_struct);
+       XMLRPC_AddValuesToVector(xReturn,
+                                                                        XMLRPC_CreateValueString(TOKEN_SOAP_FAULTCODE, fault_code, 0),
+                                                                        XMLRPC_CreateValueString(TOKEN_SOAP_FAULTSTRING, fault_string, 0),
+                                                                        XMLRPC_CreateValueString(TOKEN_SOAP_FAULTACTOR, actor, 0),
+                                                                        XMLRPC_CreateValueString(TOKEN_SOAP_FAULTDETAILS, details, 0),
+                                                                        NULL);
+       return xReturn;
+}
+
+/* translates xml soap dom to native data structures. recursive. */
+XMLRPC_VALUE xml_element_to_SOAP_REQUEST_worker(XMLRPC_REQUEST request, 
+                                                                                                                               XMLRPC_VALUE xParent,
+                                                                                                                               struct array_info* parent_array,
+                                                                                                                               XMLRPC_VALUE xCurrent, 
+                                                                                                                               xml_element* el, 
+                                                                                                                               int depth) {
+       XMLRPC_REQUEST_TYPE rtype = xmlrpc_request_none;
+
+       /* no current element on first call */
+       if (!xCurrent) {
+               xCurrent = XMLRPC_CreateValueEmpty();
+       }
+
+       /* increment recursion depth guage */
+       depth ++;
+
+       /* safety first. must have a valid element */
+       if (el && el->name) {
+               const char* id = NULL;
+               const char* type = NULL, *arrayType=NULL, *actor = NULL;
+               xml_element_attr* attr_iter = Q_Head(&el->attrs);
+               int b_must_understand = 0;
+               
+               /* in soap, types may be specified in either element name -or- with xsi:type attribute. */
+               if (is_soap_type(el->name)) {
+                       type = el->name;
+               }
+               /* if our parent node, by definition a vector, is not an array, then
+                  our element name must be our key identifier. */
+               else if (XMLRPC_GetVectorType(xParent) != xmlrpc_vector_array) {
+                       id = el->name;
+                       if(!strcmp(id, "item")) {
+                       }
+               }
+
+               /* iterate through element attributes, pick out useful stuff. */
+               while (attr_iter) {
+                       /* element's type */
+                       if (!strcmp(attr_iter->key, TOKEN_TYPE)) {
+                               type = attr_iter->val;
+                       }
+                       /* array type */
+                       else if (!strcmp(attr_iter->key, TOKEN_ARRAY_TYPE)) {
+                               arrayType = attr_iter->val;
+                       }
+                       /* must understand, sometimes present in headers. */
+                       else if (!strcmp(attr_iter->key, TOKEN_MUSTUNDERSTAND)) {
+                               b_must_understand = strchr(attr_iter->val, '1') ? 1 : 0;
+                       }
+                       /* actor, used in conjuction with must understand. */
+                       else if (!strcmp(attr_iter->key, TOKEN_ACTOR)) {
+                               actor = attr_iter->val;
+                       }
+                       attr_iter = Q_Next(&el->attrs);
+               }
+
+               /* check if caller says we must understand something in a header. */
+               if (b_must_understand) {
+                       /* is must understand actually indended for us?
+                          BUG: spec says we should also determine if actor is our URL, but
+                               we do not have that information. */
+                       if (!actor || !strcmp(actor, TOKEN_ACTOR_NEXT)) {
+                               /* TODO: implement callbacks or other mechanism for applications
+                                  to "understand" these headers. For now, we just bail if we
+                                  get a mustUnderstand header intended for us. */
+                               XMLRPC_RequestSetError(request, 
+                                                                                         gen_soap_fault("SOAP-ENV:MustUnderstand",
+                                                                                                                                 "SOAP Must Understand Error",
+                                                                                                                                 "", ""));
+                               return xCurrent;
+                       }
+               }
+
+               /* set id (key) if one was found. */
+               if (id) {
+                       XMLRPC_SetValueID_Case(xCurrent, id, 0, xmlrpc_case_exact);
+               }
+
+               /* according to soap spec, 
+                  depth 1 = Envelope, 2 = Header, Body & Fault, 3 = methodcall or response. */
+               if (depth == 3) {
+                       const char* methodname = el->name;
+                       char* p = NULL;
+
+                       /* BUG: we determine request or response type using presence of "Response" in element name.
+                          According to spec, this is only recommended, not required. Apparently, implementations
+                          are supposed to know the type of action based on state, which strikes me as a bit lame.
+                          Anyway, we don't have that state info, thus we use Response as a heuristic. */
+                       rtype =
+#ifdef strcasestr
+                       strcasestr(el->name, "response") ? xmlrpc_request_response : xmlrpc_request_call;
+#else
+                       strstr(el->name, "esponse") ? xmlrpc_request_response : xmlrpc_request_call;
+#endif
+                       XMLRPC_RequestSetRequestType(request, rtype);
+
+                       /* Get methodname.  strip xml namespace crap. */
+                       p = strchr(el->name, ':');
+                       if (p) {
+                               methodname = p + 1;
+                       }
+                       if (rtype == xmlrpc_request_call) {
+                               XMLRPC_RequestSetMethodName(request, methodname);
+                       }
+               }
+
+
+               /* Next, we begin to convert actual values. if no children, then must be a scalar value. */
+               if (!Q_Size(&el->children)) {
+                       if (!type && parent_array && parent_array->kids_type[0]) {
+                               type = parent_array->kids_type;
+                       }
+                       if (!type || !strcmp(type, TOKEN_STRING)) {
+                               XMLRPC_SetValueString(xCurrent, el->text.str, el->text.len);
+                       }
+                       else if (!strcmp(type, TOKEN_INT)) {
+                               XMLRPC_SetValueInt(xCurrent, atoi(el->text.str));
+                       }
+                       else if (!strcmp(type, TOKEN_BOOLEAN)) {
+                               XMLRPC_SetValueBoolean(xCurrent, atoi(el->text.str));
+                       }
+                       else if (!strcmp(type, TOKEN_DOUBLE) ||
+                                               !strcmp(type, TOKEN_FLOAT)) {
+                               XMLRPC_SetValueDouble(xCurrent, atof(el->text.str));
+                       }
+                       else if (!strcmp(type, TOKEN_NULL)) {
+                               /* already an empty val. do nothing. */
+                       }
+                       else if (!strcmp(type, TOKEN_DATETIME)) {
+                               XMLRPC_SetValueDateTime_ISO8601(xCurrent, el->text.str);
+                       }
+                       else if (!strcmp(type, TOKEN_BASE64)) {
+                               struct buffer_st buf;
+                               base64_decode(&buf, el->text.str, el->text.len);
+                               XMLRPC_SetValueBase64(xCurrent, buf.data, buf.offset);
+                               buffer_delete(&buf);
+                       }
+               }
+               /* Element has children, thus a vector, or "compound type" in soap-speak. */
+               else {
+                       struct array_info* ai = NULL;
+                       xml_element* iter = (xml_element*)Q_Head(&el->children);
+
+                       if (!type || !strcmp(type, TOKEN_STRUCT)) {
+                               XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_struct);
+                       }
+                       else if (!strcmp(type, TOKEN_ARRAY) || arrayType != NULL) {
+                               /* determine magic associated with soap array type.
+                                  this is passed down as we recurse, so our children have access to the info. */
+                               ai = parse_array_type_info(arrayType);  // alloc'ed ai free'd below.
+                               XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_array);
+                       }
+                       else {
+                               /* mixed is probably closest thing we have to compound type. */
+                               XMLRPC_SetIsVector(xCurrent, xmlrpc_vector_mixed);
+                       }
+                       /* Recurse, adding values as we go.  Check for error during recursion
+                          and if found, bail.  this short-circuits us out of the recursion. */
+                       while ( iter && !XMLRPC_RequestGetError(request) ) {
+                               XMLRPC_VALUE xNext = NULL;
+                               /* top level elements don't actually represent values, so we just pass the
+                                  current value along until we are deep enough. */
+                               if ( depth <= 2 ||
+                                         (rtype == xmlrpc_request_response && depth <= 3) ) {
+                                       xml_element_to_SOAP_REQUEST_worker(request, NULL, ai, xCurrent, iter, depth);
+                               }
+                               /* ready to do some actual de-serialization. create a new empty value and
+                                  pass that along to be init'd, then add it to our current vector. */
+                               else {
+                                       xNext = XMLRPC_CreateValueEmpty();
+                                       xml_element_to_SOAP_REQUEST_worker(request, xCurrent, ai, xNext, iter, depth);
+                                       XMLRPC_AddValueToVector(xCurrent, xNext);
+                               }
+                               iter = (xml_element*)Q_Next(&el->children);
+                       }
+                       /* cleanup */
+                       if (ai) {
+                               free(ai);
+                       }
+               }
+       }
+       return xCurrent;
+}
+
+/* Convert soap xml dom to XMLRPC_VALUE, sans request info.  untested. */
+XMLRPC_VALUE xml_element_to_SOAP_VALUE(xml_element* el)
+{
+       return xml_element_to_SOAP_REQUEST_worker(NULL, NULL, NULL, NULL, el, 0);
+}
+
+/* Convert soap xml dom to XMLRPC_REQUEST */
+XMLRPC_VALUE xml_element_to_SOAP_REQUEST(XMLRPC_REQUEST request, xml_element* el)
+{
+       if (request) {
+               return XMLRPC_RequestSetData(request, xml_element_to_SOAP_REQUEST_worker(request, NULL, NULL, NULL, el, 0));
+       }
+       return NULL;
+}
+
+
+/* translates data structures to soap/xml. recursive */
+xml_element* SOAP_to_xml_element_worker(XMLRPC_REQUEST request, XMLRPC_VALUE node) {
+#define BUF_SIZE 128
+       xml_element* elem_val = NULL;
+       if (node) {
+               int bFreeNode = 0;  /* sometimes we may need to free 'node' variable */
+               char buf[BUF_SIZE];
+               XMLRPC_VALUE_TYPE_EASY type = XMLRPC_GetValueTypeEasy(node);
+               char* pName = NULL, *pAttrType = NULL;
+
+               /* create our return value element */
+               elem_val = xml_elem_new();
+
+               switch (type) {
+               case xmlrpc_type_struct:
+               case xmlrpc_type_mixed:
+               case xmlrpc_type_array:
+                       if (type == xmlrpc_type_array) {
+                               /* array's are _very_ special in soap.
+                                  TODO: Should handle sparse/partial arrays here. */
+
+                               /* determine soap array type. */
+                               const char* type = get_array_soap_type(node);
+                               xml_element_attr* attr_array_type = NULL;
+
+                               /* specify array kids type and array size. */
+                               snprintf(buf, sizeof(buf), "%s[%i]", type, XMLRPC_VectorSize(node));
+                               attr_array_type = new_attr(TOKEN_ARRAY_TYPE, buf);
+
+                               Q_PushTail(&elem_val->attrs, attr_array_type);
+
+                               pAttrType = TOKEN_ARRAY;
+                       }
+                       /* check for fault, which is a rather special case. 
+                          (can't these people design anything consistent/simple/elegant?) */
+                       else if (type == xmlrpc_type_struct) {
+                               int fault_type = get_fault_type(node);
+                               if (fault_type) {
+                                       if (fault_type == 1) {
+                                               /* gen fault from xmlrpc style fault codes              
+                                                   notice that we get a new node, which must be freed herein. */
+                                               node = gen_fault_xmlrpc(node, elem_val);
+                                               bFreeNode = 1;
+                                       }
+                                       pName = TOKEN_FAULT;
+                               }
+                       }
+
+                       {
+                               /* recurse through sub-elements */
+                               XMLRPC_VALUE xIter = XMLRPC_VectorRewind(node);
+                               while ( xIter ) {
+                                       xml_element* next_el = SOAP_to_xml_element_worker(request, xIter);
+                                       if (next_el) {
+                                               Q_PushTail(&elem_val->children, next_el);
+                                       }
+                                       xIter = XMLRPC_VectorNext(node);
+                               }
+                       }
+
+                       break;
+
+                       /* handle scalar types */
+               case xmlrpc_type_empty:
+                       pAttrType = TOKEN_NULL;
+                       break;
+               case xmlrpc_type_string:
+                       pAttrType = TOKEN_STRING;
+                       simplestring_addn(&elem_val->text, XMLRPC_GetValueString(node), XMLRPC_GetValueStringLen(node));
+                       break;
+               case xmlrpc_type_int:
+                       pAttrType = TOKEN_INT;
+                       snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueInt(node));
+                       simplestring_add(&elem_val->text, buf);
+                       break;
+               case xmlrpc_type_boolean:
+                       pAttrType = TOKEN_BOOLEAN;
+                       snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueBoolean(node));
+                       simplestring_add(&elem_val->text, buf);
+                       break;
+               case xmlrpc_type_double:
+                       pAttrType = TOKEN_DOUBLE;
+                       snprintf(buf, BUF_SIZE, "%f", XMLRPC_GetValueDouble(node));
+                       simplestring_add(&elem_val->text, buf);
+                       break;
+               case xmlrpc_type_datetime:
+                       {
+                               time_t tt = XMLRPC_GetValueDateTime(node);
+                               struct tm *tm = localtime (&tt);
+                               pAttrType = TOKEN_DATETIME;
+                               if(strftime (buf, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", tm)) {
+                                       simplestring_add(&elem_val->text, buf);
+                               }
+                       }
+                       break;
+               case xmlrpc_type_base64:
+                       {
+                               struct buffer_st buf;
+                               pAttrType = TOKEN_BASE64;
+                               base64_encode(&buf, XMLRPC_GetValueBase64(node), XMLRPC_GetValueStringLen(node));
+                               simplestring_addn(&elem_val->text, buf.data, buf.offset );
+                               buffer_delete(&buf);
+                       }
+                       break;
+                       break;
+               default:
+                       break;
+               }
+
+               /* determining element's name is a bit tricky, due to soap semantics. */
+               if (!pName) {
+                       /* if the value's type is known... */
+                       if (pAttrType) {
+                               /* see if it has an id (key). If so, use that as name, and type as an attribute. */
+                               pName = (char*)XMLRPC_GetValueID(node);
+                               if (pName) {
+                                       Q_PushTail(&elem_val->attrs, new_attr(TOKEN_TYPE, pAttrType));
+                               }
+
+                               /* otherwise, use the type as the name. */
+                               else {
+                                       pName = pAttrType;
+                               }
+                       }
+                       /* if the value's type is not known... (a rare case?) */
+                       else {
+                               /* see if it has an id (key). otherwise, default to generic "item" */
+                               pName = (char*)XMLRPC_GetValueID(node);
+                               if (!pName) {
+                                       pName = "item";
+                               }
+                       }
+               }
+               elem_val->name = strdup(pName);
+
+               /* cleanup */
+               if (bFreeNode) {
+                       XMLRPC_CleanupValue(node);
+               }
+       }
+       return elem_val;
+}
+
+/* convert XMLRPC_VALUE to soap xml dom.  untested. */
+xml_element* SOAP_VALUE_to_xml_element(XMLRPC_VALUE node) {
+       return SOAP_to_xml_element_worker(NULL, node);
+}
+
+/* convert XMLRPC_REQUEST to soap xml dom. */
+xml_element* SOAP_REQUEST_to_xml_element(XMLRPC_REQUEST request) {
+       xml_element* root = xml_elem_new();
+
+       /* safety first. */
+       if (root) {
+               xml_element* body = xml_elem_new();
+               root->name = strdup("SOAP-ENV:Envelope");
+
+               /* silly namespace stuff */
+               Q_PushTail(&root->attrs, new_attr("xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"));
+               Q_PushTail(&root->attrs, new_attr("xmlns:xsi", "http://www.w3.org/1999/XMLSchema-instance"));
+               Q_PushTail(&root->attrs, new_attr("xmlns:xsd", "http://www.w3.org/1999/XMLSchema"));
+               Q_PushTail(&root->attrs, new_attr("xmlns:SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"));
+               Q_PushTail(&root->attrs, new_attr("xmlns:si", "http://soapinterop.org/xsd"));
+               Q_PushTail(&root->attrs, new_attr("xmlns:ns6", "http://testuri.org"));
+               Q_PushTail(&root->attrs, new_attr("SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/"));
+
+               /* Q_PushHead(&root->attrs, new_attr("xmlns:ks", "http://kitchen.sink.org/soap/everything/under/sun"));
+                      JUST KIDDING!! :-)  ---->                ------------------------------------------------- */
+
+               if (body) {
+                       /* go ahead and serialize first... */
+                       xml_element* el_serialized =  
+                       SOAP_to_xml_element_worker(request, 
+                                                                                               XMLRPC_RequestGetData(request));
+
+                       /* check for fault, in which case, there is no intermediate element */
+                       if (el_serialized && !strcmp(el_serialized->name, TOKEN_FAULT)) {
+                               Q_PushTail(&body->children, el_serialized);
+                       }
+                       /* usual case: not a fault. Add Response element in between. */
+                       else {
+                               xml_element* rpc = xml_elem_new();
+
+                               if (rpc) {
+                                       const char* methodname = XMLRPC_RequestGetMethodName(request);
+                                       XMLRPC_REQUEST_TYPE rtype = XMLRPC_RequestGetRequestType(request);
+
+                                       /* if we are making a request, we want to use the methodname as is. */
+                                       if (rtype == xmlrpc_request_call) {
+                                               if (methodname) {
+                                                       rpc->name = strdup(methodname);
+                                               }
+                                       }
+                                       /* if it's a response, we append "Response". Also, given xmlrpc-epi
+                                          API/architecture, it's likely that we don't have a methodname for
+                                          the response, so we have to check that. */
+                                       else {
+                                               char buf[128];
+                                               snprintf(buf, sizeof(buf), "%s%s", 
+                                                                       methodname ? methodname : "",
+                                                                       "Response");
+
+                                               rpc->name = strdup(buf);
+                                       }
+
+                                       /* add serialized data to method call/response.
+                                          add method call/response to body element */
+                                       if (rpc->name) {
+                                               if(el_serialized) {
+                                                       if(Q_Size(&el_serialized->children) && rtype == xmlrpc_request_call) {
+                                                               xml_element* iter = (xml_element*)Q_Head(&el_serialized->children);
+                                                               while(iter) {
+                                                                       Q_PushTail(&rpc->children, iter);
+                                                                       iter = (xml_element*)Q_Next(&el_serialized->children);
+                                                               }
+                                                               xml_elem_free_non_recurse(el_serialized);
+                                                       }
+                                                       else {
+                                                               Q_PushTail(&rpc->children, el_serialized);
+                                                       }
+                                               }
+
+                                               Q_PushTail(&body->children, rpc);
+                                       }
+                                       else {
+                                               /* no method name?!
+                                                  TODO: fault here...? */
+                                       }
+                               }
+                       }
+                       body->name = strdup("SOAP-ENV:Body");
+                       Q_PushTail(&root->children, body);
+               }
+       }
+
+       return root;
+}
+
diff --git a/php/xmlrpc/libxmlrpc/xml_to_soap.h b/php/xmlrpc/libxmlrpc/xml_to_soap.h
new file mode 100644 (file)
index 0000000..9ae9308
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+#ifndef XML_TO_SOAP_H
+ #define XML_TO_SOAP_H
+
+#include "xmlrpc.h"
+
+XMLRPC_VALUE xml_element_to_SOAP_VALUE(xml_element* el);
+XMLRPC_VALUE xml_element_to_SOAP_REQUEST(XMLRPC_REQUEST request, xml_element* el);
+xml_element* SOAP_VALUE_to_xml_element(XMLRPC_VALUE node);
+xml_element* SOAP_REQUEST_to_xml_element(XMLRPC_REQUEST request);
+
+#endif /* XML_TO_XMLRPC_H */
diff --git a/php/xmlrpc/libxmlrpc/xml_to_xmlrpc.c b/php/xmlrpc/libxmlrpc/xml_to_xmlrpc.c
new file mode 100644 (file)
index 0000000..2219bca
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+static const char rcsid[] = "#(@) $Id: xml_to_xmlrpc.c 5574 2007-10-25 20:33:17Z thierry $";
+
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include "xml_to_xmlrpc.h"
+#include "base64.h"
+
+/* list of tokens used in vocab */
+#define ELEM_ARRAY          "array"
+#define ELEM_BASE64         "base64"
+#define ELEM_BOOLEAN        "boolean"
+#define ELEM_DATA           "data"
+#define ELEM_DATETIME       "dateTime.iso8601"
+#define ELEM_DOUBLE         "double"
+#define ELEM_FAULT          "fault"
+#define ELEM_FAULTCODE      "faultCode"
+#define ELEM_FAULTSTRING    "faultString"
+#define ELEM_I4             "i4"
+#define ELEM_INT            "int"
+#define ELEM_MEMBER         "member"
+#define ELEM_METHODCALL     "methodCall"
+#define ELEM_METHODNAME     "methodName"
+#define ELEM_METHODRESPONSE "methodResponse"
+#define ELEM_NAME           "name"
+#define ELEM_NIL            "nil"
+#define ELEM_PARAM          "param"
+#define ELEM_PARAMS         "params"
+#define ELEM_STRING         "string"
+#define ELEM_STRUCT         "struct"
+#define ELEM_VALUE          "value"
+
+
+XMLRPC_VALUE xml_element_to_XMLRPC_REQUEST_worker(XMLRPC_REQUEST request, XMLRPC_VALUE parent_vector, XMLRPC_VALUE current_val, xml_element* el) {
+   if (!current_val) {
+      /* This should only be the case for the first element */
+      current_val = XMLRPC_CreateValueEmpty();
+   }
+
+       if (el->name) {
+
+      /* first, deal with the crazy/stupid fault format */
+      if (!strcmp(el->name, ELEM_FAULT)) {
+                       xml_element* fault_value = (xml_element*)Q_Head(&el->children);
+                       XMLRPC_SetIsVector(current_val, xmlrpc_vector_struct);
+
+         if(fault_value) {
+            xml_element* fault_struct = (xml_element*)Q_Head(&fault_value->children);
+            if(fault_struct) {
+               xml_element* iter = (xml_element*)Q_Head(&fault_struct->children);
+
+               while (iter) {
+                  XMLRPC_VALUE xNextVal = XMLRPC_CreateValueEmpty();
+                  xml_element_to_XMLRPC_REQUEST_worker(request, current_val, xNextVal, iter);
+                  XMLRPC_AddValueToVector(current_val, xNextVal);
+                  iter = (xml_element*)Q_Next(&fault_struct->children);
+               }
+            }
+         }
+      }
+               else if (!strcmp(el->name, ELEM_DATA)   /* should be ELEM_ARRAY, but there is an extra level. weird */
+                        || (!strcmp(el->name, ELEM_PARAMS) && 
+                                 (XMLRPC_RequestGetRequestType(request) == xmlrpc_request_call)) ) {           /* this "PARAMS" concept is silly.  dave?! */
+         xml_element* iter = (xml_element*)Q_Head(&el->children);
+         XMLRPC_SetIsVector(current_val, xmlrpc_vector_array);
+
+         while (iter) {
+            XMLRPC_VALUE xNextVal = XMLRPC_CreateValueEmpty();
+            xml_element_to_XMLRPC_REQUEST_worker(request, current_val, xNextVal, iter);
+            XMLRPC_AddValueToVector(current_val, xNextVal);
+            iter = (xml_element*)Q_Next(&el->children);
+         }
+               }
+               else if (!strcmp(el->name, ELEM_STRUCT)) {
+         xml_element* iter = (xml_element*)Q_Head(&el->children);
+         XMLRPC_SetIsVector(current_val, xmlrpc_vector_struct);
+
+         while ( iter ) {
+            XMLRPC_VALUE xNextVal = XMLRPC_CreateValueEmpty();
+            xml_element_to_XMLRPC_REQUEST_worker(request, current_val, xNextVal, iter);
+            XMLRPC_AddValueToVector(current_val, xNextVal);
+            iter = (xml_element*)Q_Next(&el->children);
+         }
+               }
+               else if (!strcmp(el->name, ELEM_STRING) || 
+                 (!strcmp(el->name, ELEM_VALUE) && Q_Size(&el->children) == 0)) {
+         XMLRPC_SetValueString(current_val, el->text.str, el->text.len);
+               }
+               else if (!strcmp(el->name, ELEM_NAME)) {
+         XMLRPC_SetValueID_Case(current_val, el->text.str, 0, xmlrpc_case_exact);
+               }
+               else if (!strcmp(el->name, ELEM_INT) || !strcmp(el->name, ELEM_I4)) {
+         XMLRPC_SetValueInt(current_val, atoi(el->text.str));
+               }
+               else if (!strcmp(el->name, ELEM_BOOLEAN)) {
+         XMLRPC_SetValueBoolean(current_val, atoi(el->text.str));
+               }
+               else if (!strcmp(el->name, ELEM_DOUBLE)) {
+         XMLRPC_SetValueDouble(current_val, atof(el->text.str));
+               }
+               else if (!strcmp(el->name, ELEM_DATETIME)) {
+         XMLRPC_SetValueDateTime_ISO8601(current_val, el->text.str);
+               }
+               else if (!strcmp(el->name, ELEM_BASE64)) {
+         struct buffer_st buf;
+         base64_decode(&buf, el->text.str, el->text.len);
+         XMLRPC_SetValueBase64(current_val, buf.data, buf.offset);
+         buffer_delete(&buf);
+               }
+               else {
+         xml_element* iter;
+
+         if (!strcmp(el->name, ELEM_METHODCALL)) {
+            if (request) {
+               XMLRPC_RequestSetRequestType(request, xmlrpc_request_call);
+            }
+                       }
+                       else if (!strcmp(el->name, ELEM_METHODRESPONSE)) {
+            if (request) {
+               XMLRPC_RequestSetRequestType(request, xmlrpc_request_response);
+            }
+                       }
+                       else if (!strcmp(el->name, ELEM_METHODNAME)) {
+            if (request) {
+               XMLRPC_RequestSetMethodName(request, el->text.str);
+            }
+         }
+
+         iter = (xml_element*)Q_Head(&el->children);
+         while ( iter ) {
+            xml_element_to_XMLRPC_REQUEST_worker(request, parent_vector, 
+                                                 current_val, iter);
+            iter = (xml_element*)Q_Next(&el->children);
+         }
+      }
+   }
+   return current_val;
+}
+
+XMLRPC_VALUE xml_element_to_XMLRPC_VALUE(xml_element* el)
+{
+   return xml_element_to_XMLRPC_REQUEST_worker(NULL, NULL, NULL, el);
+}
+
+XMLRPC_VALUE xml_element_to_XMLRPC_REQUEST(XMLRPC_REQUEST request, xml_element* el)
+{
+   if (request) {
+      return XMLRPC_RequestSetData(request, xml_element_to_XMLRPC_REQUEST_worker(request, NULL, NULL, el));
+   }
+   return NULL;
+}
+
+xml_element* XMLRPC_to_xml_element_worker(XMLRPC_VALUE current_vector, XMLRPC_VALUE node, 
+                                          XMLRPC_REQUEST_TYPE request_type, int depth) {
+#define BUF_SIZE 512
+   xml_element* root = NULL;
+   if (node) {
+      char buf[BUF_SIZE];
+      XMLRPC_VALUE_TYPE type = XMLRPC_GetValueType(node);
+      XMLRPC_VECTOR_TYPE vtype = XMLRPC_GetVectorType(node);
+      xml_element* elem_val = xml_elem_new();
+
+      /* special case for when root element is not an array */
+      if (depth == 0 && 
+          !(type == xmlrpc_vector && 
+            vtype == xmlrpc_vector_array && 
+            request_type == xmlrpc_request_call) ) {
+         int bIsFault = (vtype == xmlrpc_vector_struct && XMLRPC_VectorGetValueWithID(node, ELEM_FAULTCODE));
+
+         xml_element* next_el = XMLRPC_to_xml_element_worker(NULL, node, request_type, depth + 1);
+         if (next_el) {
+            Q_PushTail(&elem_val->children, next_el);
+         }
+         elem_val->name = strdup(bIsFault ? ELEM_FAULT : ELEM_PARAMS);
+               }
+               else {
+         switch (type) {
+                       case xmlrpc_empty: /*  treat null value as empty string in xmlrpc. */
+         case xmlrpc_nil:
+            elem_val->name = strdup(ELEM_NIL);
+            break;
+         case xmlrpc_string:
+            elem_val->name = strdup(ELEM_STRING);
+            simplestring_addn(&elem_val->text, XMLRPC_GetValueString(node), XMLRPC_GetValueStringLen(node));
+            break;
+         case xmlrpc_int:
+            elem_val->name = strdup(ELEM_INT);
+            snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueInt(node));
+            simplestring_add(&elem_val->text, buf);
+            break;
+         case xmlrpc_boolean:
+            elem_val->name = strdup(ELEM_BOOLEAN);
+            snprintf(buf, BUF_SIZE, "%i", XMLRPC_GetValueBoolean(node));
+            simplestring_add(&elem_val->text, buf);
+            break;
+         case xmlrpc_double:
+            elem_val->name = strdup(ELEM_DOUBLE);
+            snprintf(buf, BUF_SIZE, "%f", XMLRPC_GetValueDouble(node));
+            simplestring_add(&elem_val->text, buf);
+            break;
+         case xmlrpc_datetime:
+            elem_val->name = strdup(ELEM_DATETIME);
+            simplestring_add(&elem_val->text, XMLRPC_GetValueDateTime_ISO8601(node));
+            break;
+         case xmlrpc_base64:
+            {
+               struct buffer_st buf;
+               elem_val->name = strdup(ELEM_BASE64);
+               base64_encode(&buf, XMLRPC_GetValueBase64(node), XMLRPC_GetValueStringLen(node));
+               simplestring_addn(&elem_val->text, buf.data, buf.offset );
+               buffer_delete(&buf);
+            }
+            break;
+         case xmlrpc_vector:
+            {
+               XMLRPC_VECTOR_TYPE my_type = XMLRPC_GetVectorType(node);
+               XMLRPC_VALUE xIter = XMLRPC_VectorRewind(node);
+               xml_element* root_vector_elem = elem_val;
+
+               switch (my_type) {
+               case xmlrpc_vector_array:
+                  {
+                      if(depth == 0) {
+                         elem_val->name = strdup(ELEM_PARAMS);
+                      }
+                      else {
+                         /* Hi my name is Dave and I like to make things as confusing
+                          * as possible, thus I will throw in this 'data' element
+                          * where it absolutely does not belong just so that people
+                          * cannot code arrays and structs in a similar and straight
+                          * forward manner. Have a good day.
+                          *
+                          * GRRRRRRRRR!
+                          */
+                         xml_element* data = xml_elem_new();
+                         data->name = strdup(ELEM_DATA);
+    
+                         elem_val->name = strdup(ELEM_ARRAY);
+                         Q_PushTail(&elem_val->children, data);
+                         root_vector_elem = data;
+                      }
+                  }
+                  break;
+               case xmlrpc_vector_mixed:       /* not officially supported */
+               case xmlrpc_vector_struct:
+                  elem_val->name = strdup(ELEM_STRUCT);
+                  break;
+               default:
+                  break;
+               }
+
+               /* recurse through sub-elements */
+               while ( xIter ) {
+                  xml_element* next_el = XMLRPC_to_xml_element_worker(node, xIter, request_type, depth + 1);
+                  if (next_el) {
+                     Q_PushTail(&root_vector_elem->children, next_el);
+                  }
+                  xIter = XMLRPC_VectorNext(node);
+               }
+            }
+            break;
+         default:
+            break;
+         }
+      }
+
+      {
+         XMLRPC_VECTOR_TYPE vtype = XMLRPC_GetVectorType(current_vector);
+
+         if (depth == 1) {
+            xml_element* value = xml_elem_new();
+            value->name = strdup(ELEM_VALUE);
+
+            /* yet another hack for the "fault" crap */
+            if (XMLRPC_VectorGetValueWithID(node, ELEM_FAULTCODE)) {
+               root = value;
+                               }
+                               else {
+               xml_element* param = xml_elem_new();
+               param->name = strdup(ELEM_PARAM);
+
+               Q_PushTail(&param->children, value);
+
+               root = param;
+            }
+            Q_PushTail(&value->children, elem_val);
+                       }
+                       else if (vtype == xmlrpc_vector_struct || vtype == xmlrpc_vector_mixed) {
+            xml_element* member = xml_elem_new();
+            xml_element* name = xml_elem_new();
+            xml_element* value = xml_elem_new();
+
+            member->name = strdup(ELEM_MEMBER);
+            name->name = strdup(ELEM_NAME);
+            value->name = strdup(ELEM_VALUE);
+
+            simplestring_add(&name->text, XMLRPC_GetValueID(node));
+
+            Q_PushTail(&member->children, name);
+            Q_PushTail(&member->children, value);
+            Q_PushTail(&value->children, elem_val);
+
+            root = member;
+                       }
+                       else if (vtype == xmlrpc_vector_array) {
+            xml_element* value = xml_elem_new();
+
+            value->name = strdup(ELEM_VALUE);
+
+            Q_PushTail(&value->children, elem_val);
+
+            root = value;
+                       }
+                       else if (vtype == xmlrpc_vector_none) {
+            /* no parent.  non-op */
+            root = elem_val;
+                       }
+                       else {
+            xml_element* value = xml_elem_new();
+
+            value->name = strdup(ELEM_VALUE);
+
+            Q_PushTail(&value->children, elem_val);
+
+            root = value;
+         }
+      }
+   }
+   return root;
+}
+
+xml_element* XMLRPC_VALUE_to_xml_element(XMLRPC_VALUE node) {
+   return XMLRPC_to_xml_element_worker(NULL, node, xmlrpc_request_none, 0);
+}
+
+xml_element* XMLRPC_REQUEST_to_xml_element(XMLRPC_REQUEST request) {
+   xml_element* wrapper = NULL;
+   if (request) {
+      const char* pStr = NULL;
+      XMLRPC_REQUEST_TYPE request_type = XMLRPC_RequestGetRequestType(request);
+      XMLRPC_VALUE xParams = XMLRPC_RequestGetData(request);
+
+      wrapper = xml_elem_new();
+
+      if (request_type == xmlrpc_request_call) {
+         pStr = ELEM_METHODCALL;
+               }
+               else if (request_type == xmlrpc_request_response) {
+         pStr = ELEM_METHODRESPONSE;
+      }
+      if (pStr) {
+         wrapper->name = strdup(pStr);
+      }
+
+               if(request_type == xmlrpc_request_call) {
+      pStr = XMLRPC_RequestGetMethodName(request);
+
+      if (pStr) {
+         xml_element* method = xml_elem_new();
+         method->name = strdup(ELEM_METHODNAME);
+         simplestring_add(&method->text, pStr);
+         Q_PushTail(&wrapper->children, method);
+      }
+               }
+      if (xParams) {
+         Q_PushTail(&wrapper->children, 
+                    XMLRPC_to_xml_element_worker(NULL, XMLRPC_RequestGetData(request), XMLRPC_RequestGetRequestType(request), 0));
+               }
+               else {
+         /* Despite the spec, the xml-rpc list folk want me to send an empty params element */
+         xml_element* params = xml_elem_new();
+         params->name = strdup(ELEM_PARAMS);
+         Q_PushTail(&wrapper->children, params);
+      }
+   }
+   return wrapper;
+}
+
diff --git a/php/xmlrpc/libxmlrpc/xml_to_xmlrpc.h b/php/xmlrpc/libxmlrpc/xml_to_xmlrpc.h
new file mode 100644 (file)
index 0000000..234a153
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+#ifndef XML_TO_XMLRPC_H
+ #define XML_TO_XMLRPC_H
+
+#include "time.h" 
+#include "xmlrpc.h"
+
+XMLRPC_VALUE xml_element_to_XMLRPC_VALUE(xml_element* el);
+XMLRPC_VALUE xml_element_to_XMLRPC_REQUEST(XMLRPC_REQUEST request, xml_element* el);
+xml_element* XMLRPC_VALUE_to_xml_element(XMLRPC_VALUE node);
+xml_element* XMLRPC_REQUEST_to_xml_element(XMLRPC_REQUEST request);
+
+#endif /* XML_TO_XMLRPC_H */
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc.c b/php/xmlrpc/libxmlrpc/xmlrpc.c
new file mode 100644 (file)
index 0000000..9aca12f
--- /dev/null
@@ -0,0 +1,2963 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+static const char rcsid[] = "#(@) $Id: xmlrpc.c 5574 2007-10-25 20:33:17Z thierry $";
+
+
+/****h* ABOUT/xmlrpc
+ * NAME
+ *   XMLRPC_VALUE
+ * AUTHOR
+ *   Dan Libby, aka danda  (dan@libby.com)
+ * CREATION DATE
+ *   9/1999 - 10/2000
+ * HISTORY
+ *   $Log: xmlrpc.c,v $
+ *   Revision 1.6  2004/04/27 17:33:59  iliaa
+ *   Removed C++ style comments.
+ *
+ *   Revision 1.5  2003/12/16 21:00:21  sniper
+ *   Fix some compile warnings (patch by Joe Orton)
+ *
+ *   Revision 1.4  2002/07/05 04:43:53  danda
+ *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
+ *
+ *   Revision 1.22  2002/03/09 23:15:44  danda
+ *   add fault interrogation funcs
+ *
+ *   Revision 1.21  2002/03/09 22:27:41  danda
+ *   win32 build patches contributed by Jeff Lawson
+ *
+ *   Revision 1.20  2002/02/13 20:58:50  danda
+ *   patch to make source more windows friendly, contributed by Jeff Lawson
+ *
+ *   Revision 1.19  2001/10/12 23:25:54  danda
+ *   default to writing xmlrpc
+ *
+ *   Revision 1.18  2001/09/29 21:58:05  danda
+ *   adding cvs log to history section
+ *
+ *   10/15/2000 -- danda -- adding robodoc documentation
+ *   08/2000 -- danda -- PHP C extension that uses XMLRPC                     
+ *   08/2000 -- danda -- support for two vocabularies: danda-rpc and xml-rpc
+ *   09/1999 -- danda -- Initial API, before I even knew of standard XMLRPC vocab. Response only.
+ *   07/2000 -- danda -- wrote new implementation to be compatible with xmlrpc standard and
+ *                       incorporated some ideas from ensor, most notably the separation of
+ *                       xml dom from xmlrpc api.
+ *   06/2000 -- danda -- played with expat-ensor from www.ensor.org.  Cool, but some flaws.
+ * TODO
+ * PORTABILITY
+ *   Coded on RedHat Linux 6.2.  Builds on Solaris x86.  Should build on just
+ *   about anything with minor mods.
+ * NOTES
+ *   Welcome to XMLRPC.  For more info on the specification and history, see
+ *   http://www.xmlrpc.org.
+ *
+ *   This code aims to be a full-featured C implementation of XMLRPC.  It does not
+ *   have any networking code.  Rather, it is intended to be plugged into apps
+ *   or libraries with existing networking facilities, eg PHP, apache, perl, mozilla, 
+ *   home-brew application servers, etc.
+ *
+ *   Usage Paradigm:
+ *     The user of this library will typically be implementing either an XMLRPC server,
+ *     an XMLRPC client, or both.  The client will use the library to build an in-memory
+ *     representation of a request, and then serialize (encode) that request into XML. The
+ *     client will then send the XML to the server via external mechanism.  The server will
+ *     de-serialize the XML back into an binary representation, call the appropriate registered
+ *     method -- thereby generating a response.  The response will be serialized into XML and
+ *     sent back to the client.  The client will de-serialize it into memory, and can
+ *     iterate through the results via API.
+ *
+ *     Both the request and the response may consist of arbitrarily long, arbitrarily nested
+ *     values.  The values may be one of several types, as defined by XMLRPC_VALUE_TYPE.
+ *
+ *   Features and Architecture:
+ *     - The XML parsing (xml_element.c) is completely independent of the XMLRPC api. In fact,
+ *       it can be used as a standalone dom implementation.
+ *     - Because of this, the same XMLRPC data can be serialized into multiple xml vocabularies.
+ *       It is simply a matter of writing a transport.  So far, two transports have been defined.
+ *       The default xmlrpc vocab (xml_to_xmlrpc.c), and simple-rpc (xml_to_dandarpc.c) which is 
+ *       proprietary, but imho more readable, and nice for proprietary legacy reasons.
+ *     - Various output options, including: xml escaping via CDATA or entity, case folding,
+ *       vocab version, and character encoding.
+ *     - One to One mapping between C structures and actual values, unlike ensor which forces
+ *       one to understand the arcana of the xmlrpc vocab.
+ *     - support for mixed indexed/keyed vector types, making it more compatible with 
+ *       languages such as PHP.
+ *     - quite speedy compared to implementations written in interpreted languages. Also, uses
+ *       intelligent string handling, so not many strlen() calls, etc.
+ *     - comprehensive API for manipulation of values
+ *******/
+
+
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "queue.h"
+#include "xmlrpc.h"
+#include "expat.h"
+#include "base64.h"
+
+#include "xml_to_xmlrpc.h"
+#include "xml_to_dandarpc.h"
+#include "xml_to_soap.h"
+#include "xml_element.h"
+#include "xmlrpc_private.h"
+#include "xmlrpc_introspection_private.h"
+#include "system_methods_private.h"
+
+
+
+/*-*********************
+* Begin Time Functions *
+***********************/
+
+static int date_from_ISO8601 (const char *text, time_t * value) {
+   struct tm tm;
+   int n;
+   int i;
+       char buf[18];
+
+       if (strchr (text, '-')) {
+               char *p = (char *) text, *p2 = buf;
+               while (p && *p) {
+                       if (*p != '-') {
+                               *p2 = *p;
+                               p2++;
+                       }
+                       p++;
+               }
+               text = buf;
+       }
+
+
+   tm.tm_isdst = -1;
+
+   if(strlen(text) < 17) {
+      return -1;
+   }
+
+   n = 1000;
+   tm.tm_year = 0;
+   for(i = 0; i < 4; i++) {
+      tm.tm_year += (text[i]-'0')*n;
+      n /= 10;
+   }
+   n = 10;
+   tm.tm_mon = 0;
+   for(i = 0; i < 2; i++) {
+      tm.tm_mon += (text[i+4]-'0')*n;
+      n /= 10;
+   }
+   tm.tm_mon --;
+
+   n = 10;
+   tm.tm_mday = 0;
+   for(i = 0; i < 2; i++) {
+      tm.tm_mday += (text[i+6]-'0')*n;
+      n /= 10;
+   }
+
+   n = 10;
+   tm.tm_hour = 0;
+   for(i = 0; i < 2; i++) {
+      tm.tm_hour += (text[i+9]-'0')*n;
+      n /= 10;
+   }
+
+   n = 10;
+   tm.tm_min = 0;
+   for(i = 0; i < 2; i++) {
+      tm.tm_min += (text[i+12]-'0')*n;
+      n /= 10;
+   }
+
+   n = 10;
+   tm.tm_sec = 0;
+   for(i = 0; i < 2; i++) {
+      tm.tm_sec += (text[i+15]-'0')*n;
+      n /= 10;
+   }
+
+   tm.tm_year -= 1900;
+
+   *value = mktime(&tm);
+
+   return 0;
+
+}
+
+static int date_to_ISO8601 (time_t value, char *buf, int length) {
+   struct tm *tm;
+   tm = localtime(&value);
+#if 0  /* TODO: soap seems to favor this method. xmlrpc the latter. */
+       return strftime (buf, length, "%Y-%m-%dT%H:%M:%SZ", tm);
+#else
+   return strftime(buf, length, "%Y%m%dT%H:%M:%S", tm);
+#endif
+}
+
+/*-*******************
+* End Time Functions *
+*********************/
+
+
+/*-***************************
+* Begin XMLRPC_REQUEST funcs *
+*****************************/
+
+/****f* REQUEST/XMLRPC_RequestNew
+ * NAME
+ *   XMLRPC_RequestNew
+ * SYNOPSIS
+ *   XMLRPC_REQUEST XMLRPC_RequestNew()
+ * FUNCTION
+ *   Creates a new XMLRPC_Request data struct
+ * INPUTS
+ *   none
+ * SEE ALSO
+ *   XMLRPC_RequestFree ()
+ * SOURCE
+ */
+XMLRPC_REQUEST XMLRPC_RequestNew() {
+   XMLRPC_REQUEST xRequest = calloc(1, sizeof(STRUCT_XMLRPC_REQUEST));
+   if(xRequest) {
+      simplestring_init(&xRequest->methodName);
+   }
+   return xRequest;
+}
+
+/*******/
+
+/****f* REQUEST/XMLRPC_RequestFree
+ * NAME
+ *   XMLRPC_RequestFree
+ * SYNOPSIS
+ *   void XMLRPC_RequestFree(XMLRPC_REQUEST request, int bFreeIO)
+ * FUNCTION
+ *   Free XMLRPC Request and all sub-values
+ * INPUTS
+ *   request -- previously allocated request struct
+ *   bFreeIO -- 1 = also free request value data, if any, 0 = ignore.
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_CleanupValue ()
+ * SOURCE
+ */
+void XMLRPC_RequestFree(XMLRPC_REQUEST request, int bFreeIO) {
+   if(request) {
+      simplestring_free(&request->methodName);
+
+      if(request->io && bFreeIO) {
+         XMLRPC_CleanupValue(request->io);
+      }
+      if(request->error) {
+         XMLRPC_CleanupValue(request->error);
+      }
+      my_free(request);
+   }
+}
+
+/*******/
+
+/* Set Method Name to call */
+/****f* REQUEST/XMLRPC_RequestSetMethodName
+ * NAME
+ *   XMLRPC_RequestSetMethodName
+ * SYNOPSIS
+ *   const char* XMLRPC_RequestSetMethodName(XMLRPC_REQUEST request, const char* methodName)
+ * FUNCTION
+ *   Set name of method to call with this request.
+ * INPUTS
+ *   request -- previously allocated request struct
+ *   methodName -- name of method
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestGetMethodName ()
+ *   XMLRPC_RequestFree ()
+ * SOURCE
+ */
+const char* XMLRPC_RequestSetMethodName(XMLRPC_REQUEST request, const char* methodName) {
+   if(request) {
+      simplestring_clear(&request->methodName);
+      simplestring_add(&request->methodName, methodName);
+      return request->methodName.str;
+   }
+   return NULL;
+}
+
+/*******/
+
+/****f* REQUEST/XMLRPC_RequestGetMethodName
+ * NAME
+ *   XMLRPC_RequestGetMethodName
+ * SYNOPSIS
+ *   const char* XMLRPC_RequestGetMethodName(XMLRPC_REQUEST request)
+ * FUNCTION
+ *   Get name of method called by this request
+ * INPUTS
+ *   request -- previously allocated request struct
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestSetMethodName ()
+ *   XMLRPC_RequestFree ()
+ * SOURCE
+ */
+const char* XMLRPC_RequestGetMethodName(XMLRPC_REQUEST request) {
+   return request ? request->methodName.str : NULL;
+}
+
+/*******/
+
+/****f* REQUEST/XMLRPC_RequestSetRequestType
+ * NAME
+ *   XMLRPC_RequestSetRequestType
+ * SYNOPSIS
+ *   XMLRPC_REQUEST_TYPE XMLRPC_RequestSetRequestType(XMLRPC_REQUEST request, XMLRPC_REQUEST_TYPE type)
+ * FUNCTION
+ *   A request struct may be allocated by a caller or by xmlrpc
+ *   in response to a request.  This allows setting the
+ *   request type.
+ * INPUTS
+ *   request -- previously allocated request struct
+ *   type    -- request type [xmlrpc_method_call | xmlrpc_method_response]
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestGetRequestType ()
+ *   XMLRPC_RequestFree ()
+ *   XMLRPC_REQUEST_TYPE
+ * SOURCE
+ */
+XMLRPC_REQUEST_TYPE XMLRPC_RequestSetRequestType (XMLRPC_REQUEST request,
+                                                                                                                                 XMLRPC_REQUEST_TYPE type) {
+   if(request) {
+      request->request_type = type;
+      return request->request_type;
+   }
+   return xmlrpc_request_none;
+}
+
+/*******/
+
+/****f* REQUEST/XMLRPC_RequestGetRequestType
+ * NAME
+ *   XMLRPC_RequestGetRequestType
+ * SYNOPSIS
+ *   XMLRPC_REQUEST_TYPE XMLRPC_RequestGetRequestType(XMLRPC_REQUEST request)
+ * FUNCTION
+ *   A request struct may be allocated by a caller or by xmlrpc
+ *   in response to a request.  This allows setting the
+ *   request type.
+ * INPUTS
+ *   request -- previously allocated request struct
+ * RESULT
+ *   type    -- request type [xmlrpc_method_call | xmlrpc_method_response]
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestSetRequestType ()
+ *   XMLRPC_RequestFree ()
+ *   XMLRPC_REQUEST_TYPE
+ * SOURCE
+ */
+XMLRPC_REQUEST_TYPE XMLRPC_RequestGetRequestType(XMLRPC_REQUEST request) {
+   return request ? request->request_type : xmlrpc_request_none;
+}
+
+/*******/
+
+
+/****f* REQUEST/XMLRPC_RequestSetData
+ * NAME
+ *   XMLRPC_RequestSetData
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_RequestSetData(XMLRPC_REQUEST request, XMLRPC_VALUE data)
+ * FUNCTION
+ *   Associates a block of xmlrpc data with the request.  The
+ *   data is *not* copied.  A pointer is kept.  The caller
+ *   should be careful not to doubly free the data value,
+ *   which may optionally be free'd by XMLRPC_RequestFree().
+ * INPUTS
+ *   request -- previously allocated request struct
+ *   data    -- previously allocated data struct
+ * RESULT
+ *   XMLRPC_VALUE -- pointer to value stored, or NULL
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestGetData ()
+ *   XMLRPC_RequestFree ()
+ *   XMLRPC_REQUEST
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_RequestSetData(XMLRPC_REQUEST request, XMLRPC_VALUE data) {
+   if(request && data) {
+               if (request->io) {
+                       XMLRPC_CleanupValue (request->io);
+               }
+      request->io = XMLRPC_CopyValue(data);
+      return request->io;
+   }
+   return NULL;
+}
+
+/*******/
+
+/****f* REQUEST/XMLRPC_RequestGetData
+ * NAME
+ *   XMLRPC_RequestGetData
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_RequestGetData(XMLRPC_REQUEST request)
+ * FUNCTION
+ *   Returns data associated with request, if any.
+ * INPUTS
+ *   request -- previously allocated request struct
+ * RESULT
+ *   XMLRPC_VALUE -- pointer to value stored, or NULL
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestSetData ()
+ *   XMLRPC_RequestFree ()
+ *   XMLRPC_REQUEST
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_RequestGetData(XMLRPC_REQUEST request) {
+   return request ? request->io : NULL;
+}
+
+/*******/
+
+/****f* REQUEST/XMLRPC_RequestSetError
+ * NAME
+ *   XMLRPC_RequestSetError
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_RequestSetError(XMLRPC_REQUEST request, XMLRPC_VALUE error)
+ * FUNCTION
+ *   Associates a block of xmlrpc data, representing an error
+ *   condition, with the request. 
+ * INPUTS
+ *   request -- previously allocated request struct
+ *   error   -- previously allocated error code or struct
+ * RESULT
+ *   XMLRPC_VALUE -- pointer to value stored, or NULL
+ * NOTES
+ *   This is a private function for usage by internals only.
+ * SEE ALSO
+ *   XMLRPC_RequestGetError ()
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_RequestSetError (XMLRPC_REQUEST request, XMLRPC_VALUE error) {
+       if (request && error) {
+               if (request->error) {
+                       XMLRPC_CleanupValue (request->error);
+               }
+               request->error = XMLRPC_CopyValue (error);
+               return request->error;
+       }
+       return NULL;
+}
+
+/*******/
+
+/****f* REQUEST/XMLRPC_RequestGetError
+ * NAME
+ *   XMLRPC_RequestGetError
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_RequestGetError(XMLRPC_REQUEST request)
+ * FUNCTION
+ *   Returns error data associated with request, if any.
+ * INPUTS
+ *   request -- previously allocated request struct
+ * RESULT
+ *   XMLRPC_VALUE -- pointer to error value stored, or NULL
+ * NOTES
+ *   This is a private function for usage by internals only.
+ * SEE ALSO
+ *   XMLRPC_RequestSetError ()
+ *   XMLRPC_RequestFree ()
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_RequestGetError (XMLRPC_REQUEST request) {
+       return request ? request->error : NULL;
+}
+
+/*******/
+
+
+/****f* REQUEST/XMLRPC_RequestSetOutputOptions
+ * NAME
+ *   XMLRPC_RequestSetOutputOptions
+ * SYNOPSIS
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestSetOutputOptions(XMLRPC_REQUEST request, XMLRPC_REQUEST_OUTPUT_OPTIONS output)
+ * FUNCTION
+ *   Sets output options used for generating XML. The output struct
+ *   is copied, and may be freed by the caller.
+ * INPUTS
+ *   request -- previously allocated request struct
+ *   output  -- output options struct initialized by caller
+ * RESULT
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS -- pointer to value stored, or NULL
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestGetOutputOptions ()
+ *   XMLRPC_RequestFree ()
+ *   XMLRPC_REQUEST
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS
+ * SOURCE
+ */
+XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestSetOutputOptions(XMLRPC_REQUEST request, XMLRPC_REQUEST_OUTPUT_OPTIONS output) {
+   if(request && output) {
+               memcpy (&request->output, output,
+                                 sizeof (STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS));
+      return &request->output;
+   }
+   return NULL;
+}
+
+/*******/
+
+
+/****f* REQUEST/XMLRPC_RequestGetOutputOptions
+ * NAME
+ *   XMLRPC_RequestGetOutputOptions
+ * SYNOPSIS
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestGetOutputOptions(XMLRPC_REQUEST request)
+ * FUNCTION
+ *   Gets a pointer to output options used for generating XML.
+ * INPUTS
+ *   request -- previously allocated request struct
+ * RESULT
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS -- pointer to options stored, or NULL
+ * SEE ALSO
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestSetOutputOptions ()
+ *   XMLRPC_RequestFree ()
+ *   XMLRPC_REQUEST
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS
+ * SOURCE
+ */
+XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestGetOutputOptions(XMLRPC_REQUEST request) {
+   return request ? &request->output : NULL;
+}
+
+/*******/
+
+/*-*************************
+* End XMLRPC_REQUEST funcs *
+***************************/
+
+
+/*-***************************
+* Begin Serializiation funcs *
+*****************************/
+
+/****f* SERIALIZE/XMLRPC_VALUE_ToXML
+ * NAME
+ *   XMLRPC_VALUE_ToXML
+ * SYNOPSIS
+ *   char* XMLRPC_VALUE_ToXML(XMLRPC_VALUE val)
+ * FUNCTION
+ *   encode XMLRPC_VALUE into XML buffer.  Note that the generated
+ *   buffer will not contain a methodCall.
+ * INPUTS
+ *   val -- previously allocated XMLRPC_VALUE
+ *   buf_len -- length of returned buffer, if not null
+ * RESULT
+ *   char* -- newly allocated buffer containing XML. 
+ *   It is the caller's responsibility to free it.
+ * SEE ALSO
+ *   XMLRPC_REQUEST_ToXML ()
+ *   XMLRPC_VALUE_FromXML ()
+ *   XMLRPC_Free ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+char* XMLRPC_VALUE_ToXML(XMLRPC_VALUE val, int* buf_len) {
+   xml_element *root_elem = XMLRPC_VALUE_to_xml_element(val);
+   char* pRet = NULL;
+
+   if(root_elem) {
+      pRet = xml_elem_serialize_to_string(root_elem, NULL, buf_len);
+      xml_elem_free(root_elem);
+   }
+   return pRet;
+}
+
+/*******/
+
+/****f* SERIALIZE/XMLRPC_REQUEST_ToXML
+ * NAME
+ *   XMLRPC_REQUEST_ToXML
+ * SYNOPSIS
+ *   char* XMLRPC_REQUEST_ToXML(XMLRPC_REQUEST request)
+ * FUNCTION
+ *   encode XMLRPC_REQUEST into XML buffer
+ * INPUTS
+ *   request -- previously allocated XMLRPC_REQUEST
+ *   buf_len -- size of returned buf, if not null
+ * RESULT
+ *   char* -- newly allocated buffer containing XML. 
+ *   It is the caller's responsibility to free it.
+ * SEE ALSO
+ *   XMLRPC_REQUEST_ToXML ()
+ *   XMLRPC_REQUEST_FromXML ()
+ *   XMLRPC_Free ()
+ *   XMLRPC_VALUE_ToXML ()
+ *   XMLRPC_REQUEST
+ * SOURCE
+ */
+char* XMLRPC_REQUEST_ToXML(XMLRPC_REQUEST request, int* buf_len) {
+      char* pRet = NULL;
+       if (request) {
+               xml_element *root_elem = NULL;
+               if (request->output.version == xmlrpc_version_simple) {
+                       root_elem = DANDARPC_REQUEST_to_xml_element (request);
+               }
+               else if (request->output.version == xmlrpc_version_1_0 ||
+                                       request->output.version == xmlrpc_version_none) {
+                       root_elem = XMLRPC_REQUEST_to_xml_element (request);
+               }
+               else if (request->output.version == xmlrpc_version_soap_1_1) {
+                       root_elem = SOAP_REQUEST_to_xml_element (request);
+               }
+
+      if(root_elem) {
+                       pRet =
+                       xml_elem_serialize_to_string (root_elem,
+                                                                                                       &request->output.xml_elem_opts,
+                                                                                                       buf_len);
+         xml_elem_free(root_elem);
+      }
+   }
+       return pRet;
+}
+
+/*******/
+
+/****f* SERIALIZE/XMLRPC_VALUE_FromXML
+ * NAME
+ *   XMLRPC_VALUE_FromXML
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_VALUE_FromXML(const char* in_buf, int le
+ * FUNCTION
+ *   Retrieve XMLRPC_VALUE from XML buffer. Note that this will
+ *   ignore any methodCall.  See XMLRPC_REQUEST_FromXML
+ * INPUTS
+ *   in_buf -- character buffer containing XML
+ *   len    -- length of buffer
+ * RESULT
+ *   XMLRPC_VALUE -- newly allocated data, or NULL if error. Should
+ *   be free'd by caller.
+ * SEE ALSO
+ *   XMLRPC_VALUE_ToXML ()
+ *   XMLRPC_REQUEST_FromXML ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_VALUE_FromXML (const char *in_buf, int len, XMLRPC_REQUEST_INPUT_OPTIONS in_options) {
+   XMLRPC_VALUE xResponse = NULL;
+   XMLRPC_REQUEST req = XMLRPC_REQUEST_FromXML(in_buf, len, in_options);
+
+   if(req) {
+      xResponse = req->io;
+      XMLRPC_RequestFree(req, 0);
+   }
+   return xResponse;
+}
+
+/*******/
+
+/* map parser errors to standard xml-rpc errors */
+static XMLRPC_VALUE map_expat_errors(XML_ELEM_ERROR error) {
+   XMLRPC_VALUE xReturn = NULL;
+   if(error) {
+      XMLRPC_ERROR_CODE code;
+      char buf[1024];
+      snprintf(buf, sizeof(buf), 
+               "error occurred at line %ld, column %ld, byte index %ld", 
+                                        error->line, error->column, error->byte_index);
+
+      /* expat specific errors */
+      switch(error->parser_code) {
+      case XML_ERROR_UNKNOWN_ENCODING:
+         code = xmlrpc_error_parse_unknown_encoding;
+         break;
+      case XML_ERROR_INCORRECT_ENCODING:
+         code = xmlrpc_error_parse_bad_encoding;
+         break;
+      default:
+         code = xmlrpc_error_parse_xml_syntax;
+         break;
+      }
+      xReturn = XMLRPC_UtilityCreateFault(code, buf);
+   }
+   return xReturn;
+}
+
+/****f* SERIALIZE/XMLRPC_REQUEST_FromXML
+ * NAME
+ *   XMLRPC_REQUEST_FromXML
+ * SYNOPSIS
+ *   XMLRPC_REQUEST XMLRPC_REQUEST_FromXML(const char* in_buf, int le
+ * FUNCTION
+ *   Retrieve XMLRPC_REQUEST from XML buffer
+ * INPUTS
+ *   in_buf -- character buffer containing XML
+ *   len    -- length of buffer
+ * RESULT
+ *   XMLRPC_REQUEST -- newly allocated data, or NULL if error. Should
+ *   be free'd by caller.
+ * SEE ALSO
+ *   XMLRPC_REQUEST_ToXML ()
+ *   XMLRPC_VALUE_FromXML ()
+ *   XMLRPC_REQUEST
+ * SOURCE
+ */
+XMLRPC_REQUEST XMLRPC_REQUEST_FromXML (const char *in_buf, int len, 
+                                                                                                       XMLRPC_REQUEST_INPUT_OPTIONS in_options) {
+   XMLRPC_REQUEST request = XMLRPC_RequestNew();
+   STRUCT_XML_ELEM_ERROR error = {0};
+
+   if(request) {
+               xml_element *root_elem =
+               xml_elem_parse_buf (in_buf, len,
+                                                                 (in_options ? &in_options->xml_elem_opts : NULL),
+                                                                 &error);
+
+      if(root_elem) {
+         if(!strcmp(root_elem->name, "simpleRPC")) {
+            request->output.version = xmlrpc_version_simple;
+            xml_element_to_DANDARPC_REQUEST(request, root_elem);
+         }
+                       else if (!strcmp (root_elem->name, "SOAP-ENV:Envelope")) {
+                               request->output.version = xmlrpc_version_soap_1_1;
+                               xml_element_to_SOAP_REQUEST (request, root_elem);
+                       }
+         else {
+            request->output.version = xmlrpc_version_1_0;
+            xml_element_to_XMLRPC_REQUEST(request, root_elem);
+         }
+         xml_elem_free(root_elem);
+      }
+      else {
+         if(error.parser_error) {
+                               XMLRPC_RequestSetError (request, map_expat_errors (&error));
+         }
+      }
+   }
+
+   return request;
+}
+
+/*******/
+
+/*-************************
+* End Serialization Funcs *
+**************************/
+
+
+
+/****f* VALUE/XMLRPC_CreateValueEmpty
+ * NAME
+ *   XMLRPC_CreateValueEmpty
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueEmpty ()
+ * FUNCTION
+ *   Create an XML value to be used/modified elsewhere.
+ * INPUTS
+ * RESULT
+ *   XMLRPC_VALUE.  The new value, or NULL on failure.
+ * SEE ALSO
+ *   XMLRPC_CleanupValue ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueEmpty() {
+   XMLRPC_VALUE v = calloc(1, sizeof(STRUCT_XMLRPC_VALUE));
+   if(v) {
+#ifdef XMLRPC_DEBUG_REFCOUNT
+               printf ("calloc'd 0x%x\n", v);
+#endif
+      v->type = xmlrpc_empty;
+      simplestring_init(&v->id);
+      simplestring_init(&v->str);
+   }
+   return v;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_SetValueID_Case
+ * NAME
+ *   XMLRPC_SetValueID_Case
+ * SYNOPSIS
+ *   const char *XMLRPC_SetValueID_Case(XMLRPC_VALUE value, const char* id, int len, XMLRPC_CASE id_case)
+ * FUNCTION
+ *   Assign an ID (key) to an XMLRPC value.
+ * INPUTS
+ *   value     The xml value who's ID we will set.
+ *   id        The desired new id.
+ *   len       length of id string if known, or 0 if unknown.
+ *   id_case   one of XMLRPC_CASE
+ * RESULT
+ *   const char*  pointer to the newly allocated id string, or NULL
+ * SEE ALSO
+ *   XMLRPC_SetValueID ()
+ *   XMLRPC_GetValueID ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_CASE
+ * SOURCE
+ */
+const char *XMLRPC_SetValueID_Case(XMLRPC_VALUE value, const char* id, int len, XMLRPC_CASE id_case) {
+   const char* pRetval = NULL;
+   if(value) {
+      if(id) {
+         simplestring_clear(&value->id);
+         (len > 0) ? simplestring_addn(&value->id, id, len) :
+                     simplestring_add(&value->id, id);
+
+         /* upper or lower case string in place if required. could be a seperate func. */
+         if(id_case == xmlrpc_case_lower || id_case == xmlrpc_case_upper) {
+            int i;
+            for(i = 0; i < value->id.len; i++) {
+                                       value->id.str[i] =
+                                       (id_case ==
+                                        xmlrpc_case_lower) ? tolower (value->id.
+                                                                                                                        str[i]) : toupper (value->
+                                                                                                                                                                         id.
+                                                                                                                                                                         str[i]);
+            }
+         }
+
+         pRetval = value->id.str;
+
+#ifdef XMLRPC_DEBUG_REFCOUNT
+         printf("set value id: %s\n", pRetval);
+#endif 
+      }
+   }
+
+   return pRetval;
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_SetValueString
+ * NAME
+ *   XMLRPC_SetValueString
+ * SYNOPSIS
+ *   const char *XMLRPC_SetValueString(XMLRPC_VALUE value, const char* val, int len)
+ * FUNCTION
+ *   Assign a string value to an XMLRPC_VALUE, and set it to type xmlrpc_string
+ * INPUTS
+ *   value     The xml value who's ID we will set.
+ *   val        The desired new string val.
+ *   len       length of val string if known, or 0 if unknown.
+ * RESULT
+ *   const char*  pointer to the newly allocated value string, or NULL
+ * SEE ALSO
+ *   XMLRPC_GetValueString ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+const char *XMLRPC_SetValueString(XMLRPC_VALUE value, const char* val, int len) {
+   char *pRetval = NULL;
+   if(value && val) {
+      simplestring_clear(&value->str);
+      (len > 0) ? simplestring_addn(&value->str, val, len) :
+                  simplestring_add(&value->str, val);
+      value->type = xmlrpc_string;
+      pRetval = (char *)value->str.str;
+   }
+
+   return pRetval;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_SetValueInt
+ * NAME
+ *   XMLRPC_SetValueInt
+ * SYNOPSIS
+ *   void XMLRPC_SetValueInt(XMLRPC_VALUE value, int val)
+ * FUNCTION
+ *   Assign an int value to an XMLRPC_VALUE, and set it to type xmlrpc_int
+ * INPUTS
+ *   value     The xml value who's ID we will set.
+ *   val        The desired new integer value
+ * RESULT
+ * SEE ALSO
+ *   XMLRPC_GetValueInt ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+void XMLRPC_SetValueInt(XMLRPC_VALUE value, int val) {
+   if(value) {
+      value->type = xmlrpc_int;
+      value->i = val;
+   }
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_SetValueBoolean
+ * NAME
+ *   XMLRPC_SetValueBoolean
+ * SYNOPSIS
+ *   void XMLRPC_SetValueBoolean(XMLRPC_VALUE value, int val)
+ * FUNCTION
+ *   Assign a boolean value to an XMLRPC_VALUE, and set it to type xmlrpc_boolean
+ * INPUTS
+ *   value     The xml value who's value we will set.
+ *   val        The desired new boolean value. [0 | 1]
+ * RESULT
+ * SEE ALSO
+ *   XMLRPC_GetValueBoolean ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+void XMLRPC_SetValueBoolean(XMLRPC_VALUE value, int val) {
+   if(value) {
+      value->type = xmlrpc_boolean;
+      value->i = val ? 1 : 0;
+   }
+}
+
+/*******/
+
+
+/****f* VECTOR/XMLRPC_SetIsVector
+ * NAME
+ *   XMLRPC_SetIsVector
+ * SYNOPSIS
+ *   int XMLRPC_SetIsVector(XMLRPC_VALUE value, XMLRPC_VECTOR_TYPE type)
+ * FUNCTION
+ *   Set the XMLRPC_VALUE to be a vector (list) type.  The vector may be one of
+ *   [xmlrpc_array | xmlrpc_struct | xmlrpc_mixed].  An array has only index values.
+ *   A struct has key/val pairs.  Mixed allows both index and key/val combinations. 
+ * INPUTS
+ *   value     The xml value who's vector type we will set
+ *   type      New type of vector as enumerated by XMLRPC_VECTOR_TYPE
+ * RESULT
+ *   int       1 if successful, 0 otherwise
+ * SEE ALSO
+ *   XMLRPC_GetValueType ()
+ *   XMLRPC_GetVectorType ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VECTOR_TYPE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+int XMLRPC_SetIsVector(XMLRPC_VALUE value, XMLRPC_VECTOR_TYPE type) {
+   int bSuccess = 0;
+
+       if (value) {
+               /* we can change the type so long as nothing is currently stored. */
+               if(value->type == xmlrpc_vector) {
+                       if(value->v) {
+                               if(!Q_Size(value->v->q)) {
+                                       value->v->type = type;
+                               }
+                       }
+               }
+               else {
+      value->v = calloc(1, sizeof(STRUCT_XMLRPC_VECTOR));
+      if(value->v) {
+         value->v->q = (queue*)malloc(sizeof(queue));
+         if(value->v->q) {
+            Q_Init(value->v->q);
+            value->v->type = type;
+            value->type = xmlrpc_vector;
+            bSuccess = 1;
+         }
+      }
+   }
+       }
+
+   return bSuccess;
+}
+
+/*******/
+
+/****f* VECTOR/XMLRPC_CreateVector
+ * NAME
+ *   XMLRPC_CreateVector
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateVector(const char* id, XMLRPC_VECTOR_TYPE type)
+ * FUNCTION
+ *   Create a new vector and optionally set an id.
+ * INPUTS
+ *   id        The id of the vector, or NULL
+ *   type      New type of vector as enumerated by XMLRPC_VECTOR_TYPE
+ * RESULT
+ *   XMLRPC_VALUE  The new vector, or NULL on failure.
+ * SEE ALSO
+ *   XMLRPC_CreateValueEmpty ()
+ *   XMLRPC_SetIsVector ()
+ *   XMLRPC_GetValueType ()
+ *   XMLRPC_GetVectorType ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VECTOR_TYPE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateVector(const char* id, XMLRPC_VECTOR_TYPE type) {
+   XMLRPC_VALUE val = NULL;
+
+   val = XMLRPC_CreateValueEmpty();
+   if(val) {
+      if(XMLRPC_SetIsVector(val, type)) {
+         if(id) {
+            const char *pSVI = NULL;
+
+            pSVI = XMLRPC_SetValueID(val, id, 0);
+            if(NULL == pSVI) {
+               val = NULL;
+            }
+         }
+      }
+      else {
+         val = NULL;
+      }
+   }
+   return val;
+}
+
+/*******/
+
+
+/* Not yet implemented.
+ *
+ * This should use a hash to determine if a given target id has already
+ * been appended.  
+ *
+ * Alternately, it could walk the entire vector, but that could be quite
+ * slow for very large lists.
+ */
+static int isDuplicateEntry(XMLRPC_VALUE target, XMLRPC_VALUE source) {
+   return 0;
+}
+
+/****f* VECTOR/XMLRPC_AddValueToVector
+ * NAME
+ *   XMLRPC_AddValueToVector
+ * SYNOPSIS
+ *   int XMLRPC_AddValueToVector(XMLRPC_VALUE target, XMLRPC_VALUE source)
+ * FUNCTION
+ *   Add (append) an existing XMLRPC_VALUE to a vector.
+ * INPUTS
+ *   target    The target vector
+ *   source    The source value to append
+ * RESULT
+ *   int       1 if successful, else 0
+ * SEE ALSO
+ *   XMLRPC_AddValuesToVector ()
+ *   XMLRPC_VectorGetValueWithID_Case ()
+ *   XMLRPC_VALUE
+ * NOTES
+ *   The function will fail and return 0 if an attempt is made to add
+ *   a value with an ID into a vector of type xmlrpc_vector_array. Such
+ *   values can only be added to xmlrpc_vector_struct.
+ * SOURCE
+ */
+int XMLRPC_AddValueToVector(XMLRPC_VALUE target, XMLRPC_VALUE source) {
+   if(target && source) {
+      if(target->type == xmlrpc_vector && target->v && 
+         target->v->q && target->v->type != xmlrpc_vector_none) {
+
+         /* guard against putting value of unknown type into vector */
+         switch(source->type) {
+            case xmlrpc_empty:
+            case xmlrpc_nil:
+            case xmlrpc_base64:
+            case xmlrpc_boolean:
+            case xmlrpc_datetime:
+            case xmlrpc_double:
+            case xmlrpc_int:
+            case xmlrpc_string:
+            case xmlrpc_vector:
+               /* Guard against putting a key/val pair into an array vector */
+               if( !(source->id.len && target->v->type == xmlrpc_vector_array) ) {
+                                       if (isDuplicateEntry (target, source)
+                                                || Q_PushTail (target->v->q, XMLRPC_CopyValue (source))) {
+                     return 1;
+                  }
+               }
+               else {
+                                       fprintf (stderr,
+                                                               "xmlrpc: attempted to add key/val pair to vector of type array\n");
+               }
+               break;
+            default:
+                               fprintf (stderr,
+                                                       "xmlrpc: attempted to add value of unknown type to vector\n");
+               break;
+         }
+      }
+   }
+   return 0;
+}
+
+/*******/
+
+
+/****f* VECTOR/XMLRPC_AddValuesToVector
+ * NAME
+ *   XMLRPC_AddValuesToVector
+ * SYNOPSIS
+ *   XMLRPC_AddValuesToVector ( target, val1, val2, val3, val(n), 0 )
+ *   XMLRPC_AddValuesToVector( XMLRPC_VALUE, ... )
+ * FUNCTION
+ *   Add (append) a series of existing XMLRPC_VALUE to a vector.
+ * INPUTS
+ *   target    The target vector
+ *   ...       The source value(s) to append.  The last item *must* be 0.
+ * RESULT
+ *   int       1 if successful, else 0
+ * SEE ALSO
+ *   XMLRPC_AddValuesToVector ()
+ *   XMLRPC_VectorGetValueWithID_Case ()
+ *   XMLRPC_VALUE
+ * NOTES
+ *   This function may actually return failure after it has already modified
+ *     or added items to target.  You can not trust the state of target
+ *     if this function returns failure.
+ * SOURCE
+ */
+int XMLRPC_AddValuesToVector(XMLRPC_VALUE target, ...) {
+   int iRetval = 0;
+
+   if(target) {
+      if(target->type == xmlrpc_vector) {
+         XMLRPC_VALUE v = NULL;
+         va_list vl;
+
+         va_start(vl, target);
+
+         do {
+            v = va_arg(vl, XMLRPC_VALUE);
+            if(v) {
+               if(!XMLRPC_AddValueToVector(target, v)) {
+                  iRetval = 0;
+                  break;
+               }
+            }
+                       }
+                       while (v);
+
+         va_end(vl);
+
+         if(NULL == v) {
+            iRetval = 1;
+         }
+      }
+   }
+   return iRetval;
+}
+
+/*******/
+
+
+/****f* VECTOR/XMLRPC_VectorGetValueWithID_Case
+ * NAME
+ *   XMLRPC_VectorGetValueWithID_Case
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_VectorGetValueWithID_Case(XMLRPC_VALUE vector, const char* id, XMLRPC_CASE_COMPARISON id_case)
+ * FUNCTION
+ *   Get value from vector matching id (key)
+ * INPUTS
+ *   vector    The source vector
+ *   id        The key to find
+ *   id_case   Rule for how to match key
+ * RESULT
+ *   int       1 if successful, else 0
+ * SEE ALSO
+ *   XMLRPC_SetValueID_Case ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_CASE_COMPARISON
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_VectorGetValueWithID_Case (XMLRPC_VALUE vector, const char *id,
+                                                                                                                         XMLRPC_CASE_COMPARISON id_case) {
+   if(vector && vector->v && vector->v->q) {
+       q_iter qi = Q_Iter_Head_F(vector->v->q);
+
+       while(qi) {
+          XMLRPC_VALUE xIter = Q_Iter_Get_F(qi);
+          if(xIter && xIter->id.str) {
+             if(id_case == xmlrpc_case_sensitive) {
+                if(!strcmp(xIter->id.str, id)) {
+                   return xIter;
+                }
+             }
+             else if(id_case == xmlrpc_case_insensitive) {
+                if(!strcasecmp(xIter->id.str, id)) {
+                   return xIter;
+                }
+             }
+          }
+          qi = Q_Iter_Next_F(qi);
+       }
+   }
+   return NULL;
+}
+
+/*******/
+
+
+int XMLRPC_VectorRemoveValue(XMLRPC_VALUE vector, XMLRPC_VALUE value) {
+   if(vector && vector->v && vector->v->q && value) {
+       q_iter qi = Q_Iter_Head_F(vector->v->q);
+
+       while(qi) {
+          XMLRPC_VALUE xIter = Q_Iter_Get_F(qi);
+          if(xIter == value) {
+             XMLRPC_CleanupValue(xIter);
+             Q_Iter_Del(vector->v->q, qi);
+             return 1;
+          }
+          qi = Q_Iter_Next_F(qi);
+       }
+   }
+   return 0;
+}
+
+
+/****f* VALUE/XMLRPC_CreateValueString
+ * NAME
+ *   XMLRPC_CreateValueString
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueString(const char* id, const char* val, int len)
+ * FUNCTION
+ *   Create an XMLRPC_VALUE, and assign a string to it
+ * INPUTS
+ *   id        The id of the value, or NULL
+ *   val       The desired new string val.
+ *   len       length of val string if known, or 0 if unknown.
+ * RESULT
+ *   newly allocated XMLRPC_VALUE, or NULL
+ * SEE ALSO
+ *   XMLRPC_GetValueString ()
+ *   XMLRPC_CreateValueEmpty ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueString(const char* id, const char* val, int len) {
+   XMLRPC_VALUE value = NULL;
+   if(val) {
+      value = XMLRPC_CreateValueEmpty();
+      if(value) {
+         XMLRPC_SetValueString(value, val, len);
+         if(id) {
+            XMLRPC_SetValueID(value, id, 0);
+         }
+      }
+   }
+   return value;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_CreateValueInt
+ * NAME
+ *   XMLRPC_CreateValueInt
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueInt(const char* id, int i)
+ * FUNCTION
+ *   Create an XMLRPC_VALUE, and assign an int to it
+ * INPUTS
+ *   id        The id of the value, or NULL
+ *   i         The desired new int val.
+ * RESULT
+ *   newly allocated XMLRPC_VALUE, or NULL
+ * SEE ALSO
+ *   XMLRPC_GetValueInt ()
+ *   XMLRPC_CreateValueEmpty ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueInt(const char* id, int i) {
+   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
+   if(val) {
+      XMLRPC_SetValueInt(val, i);
+      if(id) {
+         XMLRPC_SetValueID(val, id, 0);
+      }
+   }
+   return val;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_CreateValueBoolean
+ * NAME
+ *   XMLRPC_CreateValueBoolean
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueBoolean(const char* id, int i)
+ * FUNCTION
+ *   Create an XMLRPC_VALUE, and assign an int to it
+ * INPUTS
+ *   id        The id of the value, or NULL
+ *   i         The desired new int val.
+ * RESULT
+ *   newly allocated XMLRPC_VALUE, or NULL
+ * SEE ALSO
+ *   XMLRPC_GetValueBoolean ()
+ *   XMLRPC_CreateValueEmpty ()
+ *   XMLRPC_VALUE
+ *   XMLRPC_VALUE_TYPE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueBoolean(const char* id, int i) {
+   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
+   if(val) {
+      XMLRPC_SetValueBoolean(val, i);
+      if(id) {
+         XMLRPC_SetValueID(val, id, 0);
+      }
+   }
+   return val;
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_CleanupValue
+ * NAME
+ *   XMLRPC_CleanupValue
+ * SYNOPSIS
+ *   void XMLRPC_CleanupValue(XMLRPC_VALUE value)
+ * FUNCTION
+ *   Frees all memory allocated for an XMLRPC_VALUE and any of its children (if a vector)
+ * INPUTS
+ *   value     The id of the value to be cleaned up.
+ * RESULT
+ *   void
+ * NOTES
+ *   Normally this function will be called for the topmost vector, thus free-ing
+ *   all children.  If a child of a vector is free'd first, results are undefined.
+ *   Failure to call this function *will* cause memory leaks.
+ *
+ *   Also, this function is implemented using reference counting.  Thus a value
+ *   may be added and freed from multiple parents so long as a reference is added
+ *   first using XMLRPC_CopyValue()
+ * SEE ALSO
+ *   XMLRPC_RequestFree ()
+ *   XMLRPC_CreateValueEmpty ()
+ *   XMLRPC_CopyValue()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+void XMLRPC_CleanupValue(XMLRPC_VALUE value) {
+   if(value) {
+      if(value->iRefCount > 0) {
+         value->iRefCount --;
+      }
+
+#ifdef XMLRPC_DEBUG_REFCOUNT
+      if(value->id.str) {
+                       printf ("decremented refcount of %s, now %i\n", value->id.str,
+                                         value->iRefCount);
+      }
+      else {
+                       printf ("decremented refcount of 0x%x, now %i\n", value,
+                                         value->iRefCount);
+      }
+#endif
+
+      if(value->type == xmlrpc_vector) {
+         if(value->v) {
+            if(value->iRefCount == 0) {
+               XMLRPC_VALUE cur = (XMLRPC_VALUE)Q_Head(value->v->q);
+               while( cur ) {
+                  XMLRPC_CleanupValue(cur);
+   
+                  /* Make sure some idiot didn't include a vector as a child of itself
+                   * and thus it would have already free'd these.
+                   */
+                  if(value->v && value->v->q) {
+                     cur = Q_Next(value->v->q);
+                  }
+                  else {
+                     break;
+                  }
+               }
+
+               Q_Destroy(value->v->q);
+               my_free(value->v->q);
+               my_free(value->v);
+            }
+         }
+      }
+
+
+      if(value->iRefCount == 0) {
+
+         /* guard against freeing invalid types */
+         switch(value->type) {
+            case xmlrpc_empty:
+            case xmlrpc_base64:
+            case xmlrpc_boolean:
+            case xmlrpc_datetime:
+            case xmlrpc_double:
+            case xmlrpc_int:
+            case xmlrpc_string:
+            case xmlrpc_vector:
+#ifdef XMLRPC_DEBUG_REFCOUNT
+               if(value->id.str) {
+                  printf("free'd %s\n", value->id.str);
+               }
+               else {
+                  printf("free'd 0x%x\n", value);
+               }
+#endif 
+               simplestring_free(&value->id);
+               simplestring_free(&value->str);
+
+               memset(value, 0, sizeof(STRUCT_XMLRPC_VALUE));
+               my_free(value);
+               break;
+            default:
+                               fprintf (stderr,
+                                                       "xmlrpc: attempted to free value of invalid type\n");
+               break;
+         }
+      }
+   }
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_SetValueDateTime
+ * NAME
+ *   XMLRPC_SetValueDateTime
+ * SYNOPSIS
+ *   void XMLRPC_SetValueDateTime(XMLRPC_VALUE value, time_t time)
+ * FUNCTION
+ *   Assign time value to XMLRPC_VALUE
+ * INPUTS
+ *   value     The target XMLRPC_VALUE
+ *   time      The desired new unix time value (time_t)
+ * RESULT
+ *   void
+ * SEE ALSO
+ *   XMLRPC_GetValueDateTime ()
+ *   XMLRPC_SetValueDateTime_ISO8601 ()
+ *   XMLRPC_CreateValueDateTime ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+void XMLRPC_SetValueDateTime(XMLRPC_VALUE value, time_t time) {
+   if(value) {
+      char timeBuf[30];
+      value->type = xmlrpc_datetime;
+      value->i = time;
+
+      timeBuf[0] = 0;
+
+      date_to_ISO8601(time, timeBuf, sizeof(timeBuf));
+
+      if(timeBuf[0]) {
+         simplestring_clear(&value->str);
+         simplestring_add(&value->str, timeBuf);
+      }
+   }
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_CopyValue
+ * NAME
+ *   XMLRPC_CopyValue
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CopyValue(XMLRPC_VALUE value)
+ * FUNCTION
+ *   Make a copy (reference) of an XMLRPC_VALUE
+ * INPUTS
+ *   value     The target XMLRPC_VALUE
+ * RESULT
+ *   XMLRPC_VALUE -- address of the copy
+ * SEE ALSO
+ *   XMLRPC_CleanupValue ()
+ *   XMLRPC_DupValueNew ()
+ * NOTES
+ *   This function is implemented via reference counting, so the
+ *   returned value is going to be the same as the passed in value.
+ *   The value must be freed the same number of times it is copied
+ *   or there will be a memory leak.
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CopyValue(XMLRPC_VALUE value) {
+   if(value) {
+      value->iRefCount ++;
+#ifdef XMLRPC_DEBUG_REFCOUNT
+      if(value->id.str) {
+                       printf ("incremented refcount of %s, now %i\n", value->id.str,
+                                         value->iRefCount);
+               }
+               else {
+                       printf ("incremented refcount of 0x%x, now %i\n", value,
+                                         value->iRefCount);
+      }
+#endif
+   }
+   return value;
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_DupValueNew
+ * NAME
+ *   XMLRPC_DupValueNew
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_DupValueNew(XMLRPC_VALUE value)
+ * FUNCTION
+ *   Make a duplicate (non reference) of an XMLRPC_VALUE with newly allocated mem.
+ * INPUTS
+ *   value     The source XMLRPC_VALUE to duplicate
+ * RESULT
+ *   XMLRPC_VALUE -- address of the duplicate value
+ * SEE ALSO
+ *   XMLRPC_CleanupValue ()
+ *   XMLRPC_CopyValue ()
+ * NOTES
+ *   Use this when function when you need to modify the contents of
+ *   the copied value seperately from the original.
+ *   
+ *   this function is recursive, thus the value and all of its children
+ *   (if any) will be duplicated.
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_DupValueNew (XMLRPC_VALUE xSource) {
+       XMLRPC_VALUE xReturn = NULL;
+       if (xSource) {
+               xReturn = XMLRPC_CreateValueEmpty ();
+               if (xSource->id.len) {
+                       XMLRPC_SetValueID (xReturn, xSource->id.str, xSource->id.len);
+               }
+
+               switch (xSource->type) {
+               case xmlrpc_int:
+               case xmlrpc_boolean:
+                       XMLRPC_SetValueInt (xReturn, xSource->i);
+                       break;
+               case xmlrpc_string:
+               case xmlrpc_base64:
+                       XMLRPC_SetValueString (xReturn, xSource->str.str, xSource->str.len);
+                       break;
+               case xmlrpc_datetime:
+                       XMLRPC_SetValueDateTime (xReturn, xSource->i);
+                       break;
+               case xmlrpc_double:
+                       XMLRPC_SetValueDouble (xReturn, xSource->d);
+                       break;
+               case xmlrpc_vector:
+                       {
+                               q_iter qi = Q_Iter_Head_F (xSource->v->q);
+                               XMLRPC_SetIsVector (xReturn, xSource->v->type);
+
+                               while (qi) {
+                                       XMLRPC_VALUE xIter = Q_Iter_Get_F (qi);
+                                       XMLRPC_AddValueToVector (xReturn, XMLRPC_DupValueNew (xIter));
+                                       qi = Q_Iter_Next_F (qi);
+                               }
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+       return xReturn;
+}
+
+/*******/
+
+
+
+/****f* VALUE/XMLRPC_CreateValueDateTime
+ * NAME
+ *   XMLRPC_CreateValueDateTime
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueDateTime(const char* id, time_t time)
+ * FUNCTION
+ *   Create new datetime value from time_t
+ * INPUTS
+ *   id        id of the new value, or NULL
+ *   time      The desired unix time value (time_t)
+ * RESULT
+ *   void
+ * SEE ALSO
+ *   XMLRPC_GetValueDateTime ()
+ *   XMLRPC_SetValueDateTime ()
+ *   XMLRPC_CreateValueDateTime_ISO8601 ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueDateTime(const char* id, time_t time) {
+   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
+   if(val) {
+      XMLRPC_SetValueDateTime(val, time);
+      if(id) {
+         XMLRPC_SetValueID(val, id, 0);
+      }
+   }
+   return val;
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_SetValueDateTime_ISO8601
+ * NAME
+ *   XMLRPC_SetValueDateTime_ISO8601
+ * SYNOPSIS
+ *   void XMLRPC_SetValueDateTime_ISO8601(XMLRPC_VALUE value, const char* s)
+ * FUNCTION
+ *   Set datetime value from IS08601 encoded string
+ * INPUTS
+ *   value     The target XMLRPC_VALUE
+ *   s         The desired new time value
+ * RESULT
+ *   void                                
+ * BUGS
+ *   This function currently attempts to convert the time string to a valid unix time
+ *   value before passing it. Behavior when the string is invalid or out of range
+ *   is not well defined, but will probably result in Jan 1, 1970 (0) being passed.
+ * SEE ALSO
+ *   XMLRPC_GetValueDateTime_ISO8601 ()
+ *   XMLRPC_CreateValueDateTime_ISO8601 ()
+ *   XMLRPC_CreateValueDateTime ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+void XMLRPC_SetValueDateTime_ISO8601(XMLRPC_VALUE value, const char* s) {
+   if(value) {
+      time_t time_val = 0;
+      if(s) {
+         date_from_ISO8601(s, &time_val);
+         XMLRPC_SetValueDateTime(value, time_val);
+      }
+   }
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_CreateValueDateTime_ISO8601
+ * NAME
+ *   XMLRPC_CreateValueDateTime_ISO8601
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueDateTime_ISO8601(const char* id, const char *s)
+ * FUNCTION
+ *   Create datetime value from IS08601 encoded string
+ * INPUTS
+ *   id        The id of the new value, or NULL
+ *   s         The desired new time value
+ * RESULT
+ *   newly allocated XMLRPC_VALUE, or NULL if no value created.                                
+ * BUGS
+ *   See XMLRPC_SetValueDateTime_ISO8601 ()
+ * SEE ALSO
+ *   XMLRPC_GetValueDateTime_ISO8601 ()
+ *   XMLRPC_SetValueDateTime_ISO8601 ()
+ *   XMLRPC_CreateValueDateTime ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueDateTime_ISO8601(const char* id, const char *s) {
+   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
+   if(val) {
+      XMLRPC_SetValueDateTime_ISO8601(val, s);
+      if(id) {
+         XMLRPC_SetValueID(val, id, 0);
+      }
+   }
+   return val;
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_SetValueBase64
+ * NAME
+ *   XMLRPC_SetValueBase64
+ * SYNOPSIS
+ *   void XMLRPC_SetValueBase64(XMLRPC_VALUE value, const char* s, int len)
+ * FUNCTION
+ *   Set base64 value.  Base64 is useful for transferring binary data, such as an image.
+ * INPUTS
+ *   value     The target XMLRPC_VALUE
+ *   s         The desired new binary value
+ *   len       The length of s, or NULL. If buffer is not null terminated, len *must* be passed.
+ * RESULT
+ *   void                                
+ * NOTES
+ *   Data is set/stored/retrieved as passed in, but is base64 encoded for XML transfer, and
+ *   decoded on the other side.  This is transparent to the caller.
+ * SEE ALSO
+ *   XMLRPC_GetValueBase64 ()
+ *   XMLRPC_CreateValueBase64 ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+void XMLRPC_SetValueBase64(XMLRPC_VALUE value, const char* s, int len) {
+   if(value && s) {
+      simplestring_clear(&value->str);
+      (len > 0) ? simplestring_addn(&value->str, s, len) :
+                  simplestring_add(&value->str, s);
+      value->type = xmlrpc_base64;
+   }
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_CreateValueBase64
+ * NAME
+ *   XMLRPC_CreateValueBase64
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueBase64(const char* id, const char* s, int len)
+ * FUNCTION
+ *   Create base64 value.  Base64 is useful for transferring binary data, such as an image.
+ * INPUTS
+ *   id        id of the new value, or NULL
+ *   s         The desired new binary value
+ *   len       The length of s, or NULL. If buffer is not null terminated, len *must* be passed.
+ * RESULT
+ *   newly allocated XMLRPC_VALUE, or NULL if error
+ * NOTES
+ *   See XMLRPC_SetValueBase64 ()
+ * SEE ALSO
+ *   XMLRPC_GetValueBase64 ()
+ *   XMLRPC_SetValueBase64 ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueBase64(const char* id, const char* s, int len) {
+   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
+   if(val) {
+      XMLRPC_SetValueBase64(val, s, len);
+      if(id) {
+         XMLRPC_SetValueID(val, id, 0);
+      }
+   }
+   return val;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_SetValueDouble
+ * NAME
+ *   XMLRPC_SetValueDouble
+ * SYNOPSIS
+ *   void XMLRPC_SetValueDouble(XMLRPC_VALUE value, double val)
+ * FUNCTION
+ *   Set double (floating point) value.
+ * INPUTS
+ *   value     The target XMLRPC_VALUE
+ *   val       The desired new double value
+ * RESULT
+ *   void                                
+ * SEE ALSO
+ *   XMLRPC_GetValueDouble ()
+ *   XMLRPC_CreateValueDouble ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+void XMLRPC_SetValueDouble(XMLRPC_VALUE value, double val) {
+   if(value) {
+      value->type = xmlrpc_double;
+      value->d = val;
+   }
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_CreateValueDouble
+ * NAME
+ *   XMLRPC_CreateValueDouble
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_CreateValueDouble(const char* id, double d)
+ * FUNCTION
+ *   Create double (floating point) value.
+ * INPUTS
+ *   id        id of the newly created value, or NULL
+ *   d         The desired new double value
+ * RESULT
+ *   void                                
+ * SEE ALSO
+ *   XMLRPC_GetValueDouble ()
+ *   XMLRPC_CreateValueDouble ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_CreateValueDouble(const char* id, double d) {
+   XMLRPC_VALUE val = XMLRPC_CreateValueEmpty();
+   if(val) {
+      XMLRPC_SetValueDouble(val, d);
+      if(id) {
+         XMLRPC_SetValueID(val, id, 0);
+      }
+   }
+   return val;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueString
+ * NAME
+ *   XMLRPC_GetValueString
+ * SYNOPSIS
+ *   const char* XMLRPC_GetValueString(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve string value
+ * INPUTS
+ *   value     source XMLRPC_VALUE of type xmlrpc_string
+ * RESULT
+ *   void                                
+ * SEE ALSO
+ *   XMLRPC_SetValueString ()
+ *   XMLRPC_GetValueType ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+const char* XMLRPC_GetValueString(XMLRPC_VALUE value) {
+    return ((value && value->type == xmlrpc_string) ? value->str.str : 0);
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueStringLen
+ * NAME
+ *   XMLRPC_GetValueStringLen
+ * SYNOPSIS
+ *   int XMLRPC_GetValueStringLen(XMLRPC_VALUE value)
+ * FUNCTION
+ *   determine length of string value
+ * INPUTS
+ *   value     XMLRPC_VALUE of type xmlrpc_string 
+ * RESULT
+ *   length of string, or 0
+ * NOTES
+ * SEE ALSO
+ *   XMLRPC_SetValueString ()
+ *   XMLRPC_GetValueString ()
+ * SOURCE
+ */
+int XMLRPC_GetValueStringLen(XMLRPC_VALUE value) {
+    return ((value) ? value->str.len : 0);
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueInt
+ * NAME
+ *   XMLRPC_GetValueInt
+ * SYNOPSIS
+ *   int XMLRPC_GetValueInt(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve integer value.
+ * INPUTS
+ *   value     XMLRPC_VALUE of type xmlrpc_int 
+ * RESULT
+ *   integer value or 0 if value is not valid int
+ * NOTES
+ *   use XMLRPC_GetValueType () to be sure if 0 is real return value or not
+ * SEE ALSO
+ *   XMLRPC_SetValueInt ()
+ *   XMLRPC_CreateValueInt ()
+ * SOURCE
+ */
+int XMLRPC_GetValueInt(XMLRPC_VALUE value) {
+    return ((value && value->type == xmlrpc_int) ? value->i : 0);
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueBoolean
+ * NAME
+ *   XMLRPC_GetValueBoolean
+ * SYNOPSIS
+ *   int XMLRPC_GetValueBoolean(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve boolean value.
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_boolean
+ * RESULT
+ *   boolean value or 0 if value is not valid boolean
+ * NOTES
+ *   use XMLRPC_GetValueType() to be sure if 0 is real value or not
+ * SEE ALSO
+ *   XMLRPC_SetValueBoolean ()
+ *   XMLRPC_CreateValueBoolean ()
+ * SOURCE
+ */
+int XMLRPC_GetValueBoolean(XMLRPC_VALUE value) {
+    return ((value && value->type == xmlrpc_boolean) ? value->i : 0);
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueDouble
+ * NAME
+ *   XMLRPC_GetValueDouble
+ * SYNOPSIS
+ *   double XMLRPC_GetValueDouble(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve double value
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_double
+ * RESULT
+ *   double value or 0 if value is not valid double.
+ * NOTES
+ *   use XMLRPC_GetValueType() to be sure if 0 is real value or not
+ * SEE ALSO
+ *   XMLRPC_SetValueDouble ()
+ *   XMLRPC_CreateValueDouble ()
+ * SOURCE
+ */
+double XMLRPC_GetValueDouble(XMLRPC_VALUE value) {
+    return ((value && value->type == xmlrpc_double) ? value->d : 0);
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueBase64
+ * NAME
+ *   XMLRPC_GetValueBase64
+ * SYNOPSIS
+ *   const char* XMLRPC_GetValueBase64(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve binary value
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_base64
+ * RESULT
+ *   pointer to binary value or 0 if value is not valid.
+ * SEE ALSO
+ *   XMLRPC_SetValueBase64 ()
+ *   XMLRPC_CreateValueBase64 ()
+ * NOTES
+ *   Call XMLRPC_GetValueStringLen() to retrieve real length of binary data.  strlen()
+ *   will not be accurate, as returned data may contain embedded nulls.
+ * SOURCE
+ */
+const char* XMLRPC_GetValueBase64(XMLRPC_VALUE value) {
+    return ((value && value->type == xmlrpc_base64) ? value->str.str : 0);
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueDateTime
+ * NAME
+ *   XMLRPC_GetValueDateTime
+ * SYNOPSIS
+ *   time_t XMLRPC_GetValueDateTime(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve time_t value
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_datetime
+ * RESULT
+ *   time_t value or 0 if value is not valid datetime.
+ * NOTES
+ *   use XMLRPC_GetValueType() to be sure if 0 is real value or not
+ * SEE ALSO
+ *   XMLRPC_SetValueDateTime ()
+ *   XMLRPC_GetValueDateTime_ISO8601 ()
+ *   XMLRPC_CreateValueDateTime ()
+ * SOURCE
+ */
+time_t XMLRPC_GetValueDateTime(XMLRPC_VALUE value) {
+    return (time_t)((value && value->type == xmlrpc_datetime) ? value->i : 0);
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueDateTime_IOS8601
+ * NAME
+ *   XMLRPC_GetValueDateTime_IOS8601
+ * SYNOPSIS
+ *   const char* XMLRPC_GetValueDateTime_IOS8601(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve ISO8601 formatted time value
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_datetime
+ * RESULT
+ *   const char* value or 0 if value is not valid datetime.
+ * SEE ALSO
+ *   XMLRPC_SetValueDateTime_IOS8601 ()
+ *   XMLRPC_GetValueDateTime ()
+ *   XMLRPC_CreateValueDateTime_IOS8601 ()
+ * SOURCE
+ */
+const char* XMLRPC_GetValueDateTime_ISO8601(XMLRPC_VALUE value) {
+    return ((value && value->type == xmlrpc_datetime) ? value->str.str : 0);
+}
+
+/*******/
+
+/* Get ID (key) of value or NULL */
+/****f* VALUE/XMLRPC_GetValueID
+ * NAME
+ *   XMLRPC_GetValueID
+ * SYNOPSIS
+ *   const char* XMLRPC_GetValueID(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve id (key) of value
+ * INPUTS
+ *   XMLRPC_VALUE of any type
+ * RESULT
+ *   const char* pointer to id of value, or NULL
+ * NOTES
+ * SEE ALSO
+ *   XMLRPC_SetValueID()
+ *   XMLRPC_CreateValueEmpty()
+ * SOURCE
+ */
+const char* XMLRPC_GetValueID(XMLRPC_VALUE value) {
+    return (const char*)((value && value->id.len) ? value->id.str : 0);
+}
+
+/*******/
+
+
+/****f* VECTOR/XMLRPC_VectorSize
+ * NAME
+ *   XMLRPC_VectorSize
+ * SYNOPSIS
+ *   int XMLRPC_VectorSize(XMLRPC_VALUE value)
+ * FUNCTION
+ *   retrieve size of vector
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_vector
+ * RESULT
+ *   count of items in vector
+ * NOTES
+ *   This is a cheap operation even on large vectors.  Vector size is 
+ *   maintained by queue during add/remove ops.
+ * SEE ALSO
+ *   XMLRPC_AddValueToVector ()
+ * SOURCE
+ */
+int XMLRPC_VectorSize(XMLRPC_VALUE value) {
+   int size = 0;
+   if(value && value->type == xmlrpc_vector && value->v) {
+      size = Q_Size(value->v->q);
+   }
+   return size;
+}
+
+/*******/
+
+/****f* VECTOR/XMLRPC_VectorRewind
+ * NAME
+ *   XMLRPC_VectorRewind
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_VectorRewind(XMLRPC_VALUE value)
+ * FUNCTION
+ *   reset vector to first item
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_vector
+ * RESULT
+ *   first XMLRPC_VALUE in list, or NULL if empty or error.
+ * NOTES
+ *   Be careful to rewind any vector passed in to you if you expect to
+ *   iterate through the entire list.
+ * SEE ALSO
+ *   XMLRPC_VectorNext ()
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_VectorRewind(XMLRPC_VALUE value) {
+   XMLRPC_VALUE xReturn = NULL;
+   if(value && value->type == xmlrpc_vector && value->v) {
+      xReturn = (XMLRPC_VALUE)Q_Head(value->v->q);
+   }
+   return xReturn;
+}
+
+/*******/
+
+/****f* VECTOR/XMLRPC_VectorNext
+ * NAME
+ *   XMLRPC_VectorNext
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_VectorNext(XMLRPC_VALUE value)
+ * FUNCTION
+ *   Iterate vector to next item in list.
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_vector
+ * RESULT
+ *   Next XMLRPC_VALUE in vector, or NULL if at end.
+ * NOTES
+ * SEE ALSO
+ *   XMLRPC_VectorRewind ()
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_VectorNext(XMLRPC_VALUE value) {
+   XMLRPC_VALUE xReturn = NULL;
+   if(value && value->type == xmlrpc_vector && value->v) {
+      xReturn = (XMLRPC_VALUE)Q_Next(value->v->q);
+   }
+   return xReturn;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueType
+ * NAME
+ *   XMLRPC_GetValueType
+ * SYNOPSIS
+ *   XMLRPC_VALUE_TYPE XMLRPC_GetValueType(XMLRPC_VALUE value)
+ * FUNCTION
+ *   determine data type of the XMLRPC_VALUE
+ * INPUTS
+ *   XMLRPC_VALUE target of query
+ * RESULT
+ *   data type of value as enumerated by XMLRPC_VALUE_TYPE
+ * NOTES
+ *   all values are of type xmlrpc_empty until set.
+ *   Deprecated for public use.  See XMLRPC_GetValueTypeEasy
+ * SEE ALSO
+ *   XMLRPC_SetValue*
+ *   XMLRPC_CreateValue*
+ *   XMLRPC_Append*
+ *   XMLRPC_GetValueTypeEasy ()
+ * SOURCE
+ */
+XMLRPC_VALUE_TYPE XMLRPC_GetValueType(XMLRPC_VALUE value) {
+   return value ? value->type : xmlrpc_empty;
+}
+
+/*******/
+
+/* Vector type accessor */
+/****f* VALUE/XMLRPC_GetVectorType
+ * NAME
+ *   XMLRPC_GetVectorType
+ * SYNOPSIS
+ *   XMLRPC_VECTOR_TYPE XMLRPC_GetVectorType(XMLRPC_VALUE value)
+ * FUNCTION
+ *   determine vector type of the XMLRPC_VALUE
+ * INPUTS
+ *   XMLRPC_VALUE of type xmlrpc_vector
+ * RESULT
+ *   vector type of value as enumerated by XMLRPC_VECTOR_TYPE.
+ *   xmlrpc_none if not a value.
+ * NOTES
+ *   xmlrpc_none is returned if value is not a vector
+ *   Deprecated for public use.  See XMLRPC_GetValueTypeEasy
+ * SEE ALSO
+ *   XMLRPC_SetIsVector ()
+ *   XMLRPC_GetValueType ()
+ *   XMLRPC_GetValueTypeEasy ()
+ * SOURCE
+ */
+XMLRPC_VECTOR_TYPE XMLRPC_GetVectorType(XMLRPC_VALUE value) {
+   return(value && value->v) ? value->v->type : xmlrpc_none;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetValueTypeEasy
+ * NAME
+ *   XMLRPC_GetValueTypeEasy
+ * SYNOPSIS
+ *   XMLRPC_VALUE_TYPE_EASY XMLRPC_GetValueTypeEasy(XMLRPC_VALUE value)
+ * FUNCTION
+ *   determine data type of the XMLRPC_VALUE. includes vector types.
+ * INPUTS
+ *   XMLRPC_VALUE target of query
+ * RESULT
+ *   data type of value as enumerated by XMLRPC_VALUE_TYPE_EASY
+ *   xmlrpc_type_none if not a value.
+ * NOTES
+ *   all values are of type xmlrpc_type_empty until set. 
+ * SEE ALSO
+ *   XMLRPC_SetValue*
+ *   XMLRPC_CreateValue*
+ *   XMLRPC_Append*
+ * SOURCE
+ */
+XMLRPC_VALUE_TYPE_EASY XMLRPC_GetValueTypeEasy (XMLRPC_VALUE value) {
+       if (value) {
+               switch (value->type) {
+               case xmlrpc_vector:
+                       switch (value->v->type) {
+                       case xmlrpc_vector_none:
+                               return xmlrpc_type_none;
+                       case xmlrpc_vector_struct:
+                               return xmlrpc_type_struct;
+                       case xmlrpc_vector_mixed:
+                               return xmlrpc_type_mixed;
+                       case xmlrpc_vector_array:
+                               return xmlrpc_type_array;
+                       }
+               default:
+                       /* evil cast, but we know they are the same */
+                       return(XMLRPC_VALUE_TYPE_EASY) value->type;
+               }
+       }
+       return xmlrpc_none;
+}
+
+/*******/
+
+
+
+/*-*******************
+* Begin Server Funcs *
+*********************/
+
+
+/****f* VALUE/XMLRPC_ServerCreate
+ * NAME
+ *   XMLRPC_ServerCreate
+ * SYNOPSIS
+ *   XMLRPC_SERVER XMLRPC_ServerCreate()
+ * FUNCTION
+ *   Allocate/Init XMLRPC Server Resources.
+ * INPUTS
+ *   none
+ * RESULT
+ *   newly allocated XMLRPC_SERVER
+ * NOTES
+ * SEE ALSO
+ *   XMLRPC_ServerDestroy ()
+ *   XMLRPC_GetGlobalServer ()
+ * SOURCE
+ */
+XMLRPC_SERVER XMLRPC_ServerCreate() {
+   XMLRPC_SERVER server = calloc(1, sizeof(STRUCT_XMLRPC_SERVER));
+   if(server) {
+      Q_Init(&server->methodlist);
+      Q_Init(&server->docslist);
+
+      /* register system methods */
+      xsm_register(server);
+   }
+   return server;
+}
+
+/*******/
+
+/* Return global server.  Not locking! Not Thread Safe! */
+/****f* VALUE/XMLRPC_GetGlobalServer
+ * NAME
+ *   XMLRPC_GetGlobalServer
+ * SYNOPSIS
+ *   XMLRPC_SERVER XMLRPC_GetGlobalServer()
+ * FUNCTION
+ *   Allocates a global (process-wide) server, or returns pointer if pre-existing.
+ * INPUTS
+ *   none
+ * RESULT
+ *   pointer to global server, or 0 if error.
+ * NOTES
+ *   ***WARNING*** This function is not thread safe.  It is included only for the very lazy.
+ *   Multi-threaded programs that use this may experience problems.
+ * BUGS
+ *   There is currently no way to cleanup the global server gracefully.
+ * SEE ALSO
+ *   XMLRPC_ServerCreate ()
+ * SOURCE
+ */
+XMLRPC_SERVER XMLRPC_GetGlobalServer() {
+   static XMLRPC_SERVER xsServer = 0;
+   if(!xsServer) {
+      xsServer = XMLRPC_ServerCreate();
+   }
+   return xsServer;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_ServerDestroy
+ * NAME
+ *   XMLRPC_ServerDestroy
+ * SYNOPSIS
+ *   void XMLRPC_ServerDestroy(XMLRPC_SERVER server)
+ * FUNCTION
+ *   Free Server Resources
+ * INPUTS
+ *   server     The server to be free'd
+ * RESULT
+ *   void
+ * NOTES
+ *   This frees the server struct and any methods that have been added.
+ * SEE ALSO
+ *   XMLRPC_ServerCreate ()
+ * SOURCE
+ */
+void XMLRPC_ServerDestroy(XMLRPC_SERVER server) {
+   if(server) {
+      doc_method* dm = Q_Head(&server->docslist);
+      server_method* sm = Q_Head(&server->methodlist);
+      while( dm ) {
+         my_free(dm);
+         dm = Q_Next(&server->docslist);
+      }
+      while( sm ) {
+         if(sm->name) {
+            my_free(sm->name);
+         }
+         if(sm->desc) {
+            XMLRPC_CleanupValue(sm->desc);
+         }
+         my_free(sm);
+         sm = Q_Next(&server->methodlist);
+      }
+      if(server->xIntrospection) {
+         XMLRPC_CleanupValue(server->xIntrospection);
+      }
+
+      Q_Destroy(&server->methodlist);
+      Q_Destroy(&server->docslist);
+      my_free(server);
+   }
+}
+
+/*******/
+
+
+/****f* VALUE/XMLRPC_ServerRegisterMethod
+ * NAME
+ *   XMLRPC_ServerRegisterMethod
+ * SYNOPSIS
+ *   void XMLRPC_ServerRegisterMethod(XMLRPC_SERVER server, const char *name, XMLRPC_Callback cb)
+ * FUNCTION
+ *   Register new XMLRPC method with server
+ * INPUTS
+ *   server     The XMLRPC_SERVER to register the method with
+ *   name       public name of the method
+ *   cb         C function that implements the method
+ * RESULT
+ *   int  - 1 if success, else 0
+ * NOTES
+ *   A C function must be registered for every "method" that the server recognizes.  The
+ *   method name is equivalent to <methodCall><name> method name </name></methodCall> in the
+ *   XML syntax.
+ * SEE ALSO
+ *   XMLRPC_ServerFindMethod ()
+ *   XMLRPC_ServerCallMethod ()
+ * SOURCE
+ */
+int XMLRPC_ServerRegisterMethod(XMLRPC_SERVER server, const char *name, XMLRPC_Callback cb) {
+   if(server && name && cb) {
+
+      server_method* sm = malloc(sizeof(server_method));
+      
+      if(sm) {
+         sm->name = strdup(name);
+         sm->method = cb;
+         sm->desc = NULL;
+
+         return Q_PushTail(&server->methodlist, sm);
+      }
+   }
+   return 0;
+}
+
+/*******/
+
+server_method* find_method(XMLRPC_SERVER server, const char* name) {
+   server_method* sm;
+
+   q_iter qi = Q_Iter_Head_F(&server->methodlist);
+
+   while( qi ) {
+      sm = Q_Iter_Get_F(qi);
+      if(sm && !strcmp(sm->name, name)) {
+         return sm;
+      }
+      qi = Q_Iter_Next_F(qi);
+   }
+   return NULL;
+}
+
+
+const char* type_to_str(XMLRPC_VALUE_TYPE type, XMLRPC_VECTOR_TYPE vtype) {
+    switch(type) {
+       case xmlrpc_none:
+          return "none";
+       case xmlrpc_empty:
+          return "empty";
+       case xmlrpc_nil:
+          return "nil";
+       case xmlrpc_base64:
+          return "base64";
+       case xmlrpc_boolean:
+          return "boolean";
+       case xmlrpc_datetime:
+          return "datetime";
+       case xmlrpc_double:
+          return "double";
+       case xmlrpc_int:
+          return "int";
+       case xmlrpc_string:
+          return "string";
+       case xmlrpc_vector:
+          switch(vtype) {
+             case xmlrpc_vector_none:
+                return "none";
+             case xmlrpc_vector_array:
+                return "array";
+             case xmlrpc_vector_mixed:
+                return "mixed vector (struct)";
+             case xmlrpc_vector_struct:
+                return "struct";
+          }
+    }
+    return "unknown";
+}
+
+/****f* VALUE/XMLRPC_ServerFindMethod
+ * NAME
+ *   XMLRPC_ServerFindMethod
+ * SYNOPSIS
+ *   XMLRPC_Callback XMLRPC_ServerFindMethod(XMLRPC_SERVER server, const char* callName)
+ * FUNCTION
+ *   retrieve C callback associated with a given method name.
+ * INPUTS       
+ *   server     The XMLRPC_SERVER the method is registered with
+ *   callName   the method to find
+ * RESULT
+ *   previously registered XMLRPC_Callback, or NULL
+ * NOTES
+ *   Typically, this is used to determine if a requested method exists, without actually calling it.
+ * SEE ALSO
+ *   XMLRPC_ServerCallMethod ()
+ *   XMLRPC_ServerRegisterMethod ()
+ * SOURCE
+ */
+XMLRPC_Callback XMLRPC_ServerFindMethod(XMLRPC_SERVER server, const char* callName) {
+   if(server && callName) {
+      q_iter qi = Q_Iter_Head_F(&server->methodlist);
+      while( qi ) {
+         server_method* sm = Q_Iter_Get_F(qi);
+         if(sm && !strcmp(sm->name, callName)) {
+            return sm->method;
+         }
+         qi = Q_Iter_Next_F(qi);
+      }
+   }
+   return NULL;
+}
+
+/*******/
+
+
+/* Call method specified in request */
+/****f* VALUE/XMLRPC_ServerCallMethod
+ * NAME
+ *   XMLRPC_ServerCallMethod
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_ServerCallMethod(XMLRPC_SERVER server, XMLRPC_REQUEST request, void* userData)
+ * FUNCTION
+ *
+ * INPUTS
+ *   server     The XMLRPC_SERVER the method is registered with
+ *   request    the request to handle
+ *   userData   any additional data to pass to the C callback, or NULL
+ * RESULT
+ *   XMLRPC_VALUE allocated by the callback, or NULL
+ * NOTES
+ *   It is typically the caller's responsibility to free the returned value.
+ *
+ *   Often the caller will want to serialize the result as XML, via 
+ *   XMLRPC_VALUE_To_XML () or XMLRPC_REQUEST_To_XML ()
+ * SEE ALSO
+ *   XMLRPC_ServerFindMethod ()
+ *   XMLRPC_ServerRegisterMethod ()
+ *   XMLRPC_CleanupValue ()
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_ServerCallMethod(XMLRPC_SERVER server, XMLRPC_REQUEST request, void* userData) {
+   XMLRPC_VALUE xReturn = NULL;
+
+   /* check for error set during request parsing / generation */
+   if(request && request->error) {
+      xReturn = XMLRPC_CopyValue(request->error);
+   }
+       else if (server && request) {
+               XMLRPC_Callback cb =
+               XMLRPC_ServerFindMethod (server, request->methodName.str);
+      if(cb) {
+         xReturn = cb(server, request, userData);
+      }
+      else {
+                       xReturn =
+                       XMLRPC_UtilityCreateFault (xmlrpc_error_unknown_method,
+                                                                                               request->methodName.str);
+      }
+   }
+   return xReturn;
+}
+
+/*******/
+
+/*-*****************
+* End server funcs *
+*******************/
+
+
+/*-***********************************
+* Begin XMLRPC General Options funcs *
+*************************************/
+
+/* For options used by XMLRPC_VALUE funcs that otherwise do not have
+ * parameters for options.  Kind of gross.  :(
+ */
+typedef struct _xmlrpc_options {
+   XMLRPC_CASE id_case;
+   XMLRPC_CASE_COMPARISON id_case_compare;
+}
+STRUCT_XMLRPC_OPTIONS, *XMLRPC_OPTIONS;
+
+static XMLRPC_OPTIONS XMLRPC_GetDefaultOptions() {
+   static STRUCT_XMLRPC_OPTIONS options = {
+      xmlrpc_case_exact,
+      xmlrpc_case_sensitive
+   };
+   return &options;
+}
+
+/****f* VALUE/XMLRPC_GetDefaultIdCase
+ * NAME
+ *   XMLRPC_GetDefaultIdCase
+ * SYNOPSIS
+ *   XMLRPC_CASE XMLRPC_GetDefaultIdCase()
+ * FUNCTION
+ *   Gets default case options used by XMLRPC_VALUE funcs
+ * INPUTS
+ *   none
+ * RESULT
+ *   XMLRPC_CASE
+ * BUGS
+ *   Nasty and gross.  Should be server specific, but that requires changing all
+ *  the XMLRPC_VALUE api's.
+ * SEE ALSO
+ *   XMLRPC_SetDefaultIdCase ()
+ * SOURCE
+ */
+XMLRPC_CASE XMLRPC_GetDefaultIdCase() {
+   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
+   return options->id_case;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_SetDefaultIdCase
+ * NAME
+ *   XMLRPC_SetDefaultIdCase
+ * SYNOPSIS
+ *   XMLRPC_CASE XMLRPC_SetDefaultIdCase(XMLRPC_CASE id_case)
+ * FUNCTION
+ *   Sets default case options used by XMLRPC_VALUE funcs
+ * INPUTS
+ *   id_case   case options as enumerated by XMLRPC_CASE
+ * RESULT
+ *   XMLRPC_CASE -- newly set option
+ * BUGS
+ *   Nasty and gross.  Should be server specific, but that requires changing all
+ *  the XMLRPC_VALUE api's.
+ * SEE ALSO
+ *   XMLRPC_GetDefaultIdCase ()
+ * SOURCE
+ */
+XMLRPC_CASE XMLRPC_SetDefaultIdCase(XMLRPC_CASE id_case) {
+   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
+   options->id_case = id_case;
+   return options->id_case;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_GetDefaultIdCaseComparison
+ * NAME
+ *   XMLRPC_GetDefaultIdCaseComparison
+ * SYNOPSIS
+ *   XMLRPC_CASE XMLRPC_GetDefaultIdCaseComparison( )
+ * FUNCTION
+ *   Gets default case comparison options used by XMLRPC_VALUE funcs
+ * INPUTS
+ *   none
+ * RESULT
+ *   XMLRPC_CASE_COMPARISON default
+ * BUGS
+ *   Nasty and gross.  Should be server specific, but that requires changing all
+ *  the XMLRPC_VALUE api's.
+ * SEE ALSO
+ *   XMLRPC_SetDefaultIdCaseComparison ()
+ * SOURCE
+ */
+XMLRPC_CASE_COMPARISON XMLRPC_GetDefaultIdCaseComparison() {
+   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
+   return options->id_case_compare;
+}
+
+/*******/
+
+/****f* VALUE/XMLRPC_SetDefaultIdCaseComparison
+ * NAME
+ *   XMLRPC_SetDefaultIdCaseComparison
+ * SYNOPSIS
+ *   XMLRPC_CASE XMLRPC_SetDefaultIdCaseComparison( XMLRPC_CASE_COMPARISON id_case_compare )
+ * FUNCTION
+ *   Gets default case comparison options used by XMLRPC_VALUE funcs
+ * INPUTS
+ *   id_case_compare  case comparison rule to set as default
+ * RESULT
+ *   XMLRPC_CASE_COMPARISON newly set default
+ * BUGS
+ *   Nasty and gross.  Should be server specific, but that requires changing all
+ *  the XMLRPC_VALUE api's.
+ * SEE ALSO
+ *   XMLRPC_GetDefaultIdCaseComparison ()
+ * SOURCE
+ */
+XMLRPC_CASE_COMPARISON XMLRPC_SetDefaultIdCaseComparison(XMLRPC_CASE_COMPARISON id_case_compare) {
+   XMLRPC_OPTIONS options = XMLRPC_GetDefaultOptions();
+   options->id_case_compare = id_case_compare;
+   return options->id_case_compare;
+}
+
+/*******/
+
+/*-*********************************
+* End XMLRPC General Options funcs *
+***********************************/
+
+
+/*-******************
+* Fault API funcs   *
+********************/
+
+/****f* UTILITY/XMLRPC_UtilityCreateFault
+ * NAME
+ *   XMLRPC_UtilityCreateFault
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_UtilityCreateFault( int fault_code, const char* fault_string )
+ * FUNCTION
+ *   generates a struct containing a string member with id "faultString" and an int member
+ *   with id "faultCode". When using the xmlrpc xml serialization, these will be translated
+ *   to <fault><value><struct>... format.
+ * INPUTS
+ *   fault_code     application specific error code. can be 0.
+ *   fault_string   application specific error string.  cannot be null.
+ * RESULT
+ *   XMLRPC_VALUE a newly created struct vector representing the error, or null on error.
+ * NOTES
+ *   This is a utility function. xmlrpc "faults" are not directly represented in this xmlrpc
+ *   API or data structures. It is the author's view, that this API is intended for simple
+ *   data types, and a "fault" is a complex data type consisting of multiple simple data
+ *   types.  This function is provided for convenience only, the same result could be
+ *   achieved directly by the application.
+ *
+ *   This function now supports some "standardized" fault codes, as specified at.
+ *   http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php.
+ *   If one of these fault codes is received, the description string will automatically
+ *   be prefixed with a standard error string and 2 newlines.  
+ *
+ *   The actual transformation between this complex type and the xml "<fault>" element takes
+ *   place in the xmlrpc to xml serialization layer.  This step is not performed when using the
+ *   simplerpc serialization, meaning that there will be no "<fault>" element in that
+ *   serialization. There will simply be a standard struct with 2 child elements.  
+ *   imho, the "<fault>" element is unnecessary and/or out of place as part of the standard API.
+ *
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_UtilityCreateFault(int fault_code, const char* fault_string) {
+   XMLRPC_VALUE xOutput = NULL;
+
+   char* string = NULL;
+   simplestring description;
+   simplestring_init(&description);
+
+   switch (fault_code) {
+       case xmlrpc_error_parse_xml_syntax:
+               string = xmlrpc_error_parse_xml_syntax_str;
+               break;
+       case xmlrpc_error_parse_unknown_encoding:
+               string = xmlrpc_error_parse_unknown_encoding_str;
+               break;
+       case xmlrpc_error_parse_bad_encoding:
+               string = xmlrpc_error_parse_bad_encoding_str;
+               break;
+       case xmlrpc_error_invalid_xmlrpc:
+               string = xmlrpc_error_invalid_xmlrpc_str;
+               break;
+       case xmlrpc_error_unknown_method:
+               string = xmlrpc_error_unknown_method_str;
+               break;
+       case xmlrpc_error_invalid_params:
+               string = xmlrpc_error_invalid_params_str;
+               break;
+       case xmlrpc_error_internal_server:
+               string = xmlrpc_error_internal_server_str;
+               break;
+       case xmlrpc_error_application:
+               string = xmlrpc_error_application_str;
+               break;
+       case xmlrpc_error_system:
+               string = xmlrpc_error_system_str;
+               break;
+       case xmlrpc_error_transport:
+               string = xmlrpc_error_transport_str;
+               break;
+   }
+
+   simplestring_add(&description, string);
+
+   if(string && fault_string) {
+      simplestring_add(&description, "\n\n");
+   }
+   simplestring_add(&description, fault_string);
+
+
+   if(description.len) {
+      xOutput = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+
+               XMLRPC_VectorAppendString (xOutput, "faultString", description.str,
+                                                                                       description.len);
+      XMLRPC_VectorAppendInt(xOutput, "faultCode", fault_code);
+   }
+
+   simplestring_free(&description);
+
+   return xOutput;
+}
+
+/*******/
+
+
+/****f* FAULT/XMLRPC_ValueIsFault
+ * NAME
+ *   XMLRPC_ValueIsFault
+ * SYNOPSIS
+ *   int XMLRPC_ValueIsFault (XMLRPC_VALUE value)
+ * FUNCTION
+ *   Determines if a value encapsulates a fault "object"
+ * INPUTS
+ *   value  any XMLRPC_VALUE
+ * RESULT
+ *   1 if it is a fault, else 0
+ * SEE ALSO
+ *   XMLRPC_ResponseIsFault ()
+ * SOURCE
+ */
+int XMLRPC_ValueIsFault (XMLRPC_VALUE value) {
+   if( XMLRPC_VectorGetValueWithID(value, "faultCode") &&
+       XMLRPC_VectorGetValueWithID(value, "faultString") ) {
+      return 1;
+   }
+   return 0;
+}
+/*******/
+
+
+/****f* FAULT/XMLRPC_ResponseIsFault
+ * NAME
+ *   XMLRPC_ResponseIsFault
+ * SYNOPSIS
+ *   int XMLRPC_ResponseIsFault (XMLRPC_REQUEST response)
+ * FUNCTION
+ *   Determines if a response contains an encapsulated fault "object"
+ * INPUTS
+ *   value  any XMLRPC_REQUEST. typically of type xmlrpc_request_response
+ * RESULT
+ *   1 if it contains a fault, else 0
+ * SEE ALSO
+ *   XMLRPC_ValueIsFault ()
+ * SOURCE
+ */
+int XMLRPC_ResponseIsFault(XMLRPC_REQUEST response) {
+   return XMLRPC_ValueIsFault( XMLRPC_RequestGetData(response) );
+}
+
+/*******/
+
+/****f* FAULT/XMLRPC_GetValueFaultCode
+ * NAME
+ *   XMLRPC_GetValueFaultCode
+ * SYNOPSIS
+ *   int XMLRPC_GetValueFaultCode (XMLRPC_VALUE value)
+ * FUNCTION
+ *   returns fault code from a struct, if any
+ * INPUTS
+ *   value  XMLRPC_VALUE of type xmlrpc_vector_struct.
+ * RESULT
+ *   fault code, else 0.
+ * BUGS
+ *   impossible to distinguish faultCode == 0 from faultCode not present.
+ * SEE ALSO
+ *   XMLRPC_GetResponseFaultCode ()
+ * SOURCE
+ */
+int XMLRPC_GetValueFaultCode (XMLRPC_VALUE value) {
+   return XMLRPC_VectorGetIntWithID(value, "faultCode");
+}
+
+/*******/
+
+/****f* FAULT/XMLRPC_GetResponseFaultCode
+ * NAME
+ *   XMLRPC_GetResponseFaultCode
+ * SYNOPSIS
+ *   int XMLRPC_GetResponseFaultCode(XMLRPC_REQUEST response)
+ * FUNCTION
+ *   returns fault code from a response, if any
+ * INPUTS
+ *   response  XMLRPC_REQUEST. typically of type xmlrpc_request_response.
+ * RESULT
+ *   fault code, else 0.
+ * BUGS
+ *   impossible to distinguish faultCode == 0 from faultCode not present.
+ * SEE ALSO
+ *   XMLRPC_GetValueFaultCode ()
+ * SOURCE
+ */
+int XMLRPC_GetResponseFaultCode(XMLRPC_REQUEST response) {
+   return XMLRPC_GetValueFaultCode( XMLRPC_RequestGetData(response) );
+}
+
+/*******/
+
+
+/****f* FAULT/XMLRPC_GetValueFaultString
+ * NAME
+ *   XMLRPC_GetValueFaultString
+ * SYNOPSIS
+ *   const char* XMLRPC_GetValueFaultString (XMLRPC_VALUE value)
+ * FUNCTION
+ *   returns fault string from a struct, if any
+ * INPUTS
+ *   value  XMLRPC_VALUE of type xmlrpc_vector_struct.
+ * RESULT
+ *   fault string, else 0.
+ * SEE ALSO
+ *   XMLRPC_GetResponseFaultString ()
+ * SOURCE
+ */
+const char* XMLRPC_GetValueFaultString (XMLRPC_VALUE value) {
+   return XMLRPC_VectorGetStringWithID(value, "faultString");
+}
+
+/*******/
+
+/****f* FAULT/XMLRPC_GetResponseFaultString
+ * NAME
+ *   XMLRPC_GetResponseFaultString
+ * SYNOPSIS
+ *   const char* XMLRPC_GetResponseFaultString (XMLRPC_REQUEST response)
+ * FUNCTION
+ *   returns fault string from a response, if any
+ * INPUTS
+ *   response  XMLRPC_REQUEST. typically of type xmlrpc_request_response.
+ * RESULT
+ *   fault string, else 0.
+ * SEE ALSO
+ *   XMLRPC_GetValueFaultString ()
+ * SOURCE
+ */
+const char* XMLRPC_GetResponseFaultString (XMLRPC_REQUEST response) {
+   return XMLRPC_GetValueFaultString( XMLRPC_RequestGetData(response) );
+}
+
+/*******/
+
+
+/*-******************
+* Utility API funcs *
+********************/
+
+
+/****f* UTILITY/XMLRPC_Free
+ * NAME
+ *   XMLRPC_Free
+ * SYNOPSIS
+ *   void XMLRPC_Free(void* mem)
+ * FUNCTION
+ *   frees a block of memory allocated by xmlrpc. 
+ * INPUTS
+ *   mem    memory to free
+ * RESULT
+ *   void
+ * NOTES
+ *   Useful for OS's where memory must be free'd
+ *   in the same library in which it is allocated.
+ * SOURCE
+ */
+void XMLRPC_Free(void* mem) {
+   my_free(mem);
+}
+
+/*******/
+
+
+/****f* UTILITY/XMLRPC_GetVersionString
+ * NAME
+ *   XMLRPC_GetVersionString
+ * SYNOPSIS
+ *   const char* XMLRPC_GetVersionString()
+ * FUNCTION
+ *   returns library version string
+ * INPUTS
+ *   
+ * RESULT
+ *   const char* 
+ * NOTES
+ * SOURCE
+ */
+const char*  XMLRPC_GetVersionString() {
+   return XMLRPC_VERSION_STR;
+}
+
+/*******/
+
+
+/*-**********************
+* End Utility API funcs *
+************************/
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc.h b/php/xmlrpc/libxmlrpc/xmlrpc.h
new file mode 100644 (file)
index 0000000..eead11c
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+#ifndef XMLRPC_ALREADY_INCLUDED
+#define XMLRPC_ALREADY_INCLUDED 1
+
+/* includes */
+#include "xml_element.h"
+#include <time.h> /* for time_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* allow version to be specified via compile line define */
+#ifndef XMLRPC_LIB_VERSION
+ #define XMLRPC_LIB_VERSION "0.51"
+#endif
+
+/* this number, representing the date, must be increased each time the API changes */
+#define XMLRPC_API_NO 20020623
+
+/* this string should be changed with each packaged release */
+#define XMLRPC_VERSION_STR "xmlrpc-epi v. " XMLRPC_LIB_VERSION
+
+/* where to find more info. shouldn't need to change much */
+#define XMLRPC_HOME_PAGE_STR "http://xmlprc-epi.sourceforge.net/"
+
+
+/****d* VALUE/XMLRPC_VALUE_TYPE
+ * NAME
+ *   XMLRPC_VALUE_TYPE
+ * NOTES
+ *   Defines data types for XMLRPC_VALUE
+ *   Deprecated for public use.  See XMLRPC_VALUE_TYPE_EASY
+ * SEE ALSO
+ *   XMLRPC_VECTOR_TYPE
+ *   XMLRPC_REQUEST_TYPE
+ * SOURCE
+ */
+typedef enum _XMLRPC_VALUE_TYPE {
+   xmlrpc_none,                   /* not a value                    */
+   xmlrpc_empty,                  /* empty value                    */
+   xmlrpc_nil,                    /* null value, eg NULL            */
+   xmlrpc_base64,                 /* base64 value, eg binary data   */
+   xmlrpc_boolean,                /* boolean  [0 | 1]               */
+   xmlrpc_datetime,               /* datetime [ISO8601 | time_t]    */
+   xmlrpc_double,                 /* double / floating point        */
+   xmlrpc_int,                    /* integer                        */
+   xmlrpc_string,                 /* string                         */
+   xmlrpc_vector                  /* vector, aka list, array        */
+} XMLRPC_VALUE_TYPE;
+/*******/
+
+/****d* VALUE/XMLRPC_VECTOR_TYPE
+ * NAME
+ *   XMLRPC_VECTOR_TYPE
+ * NOTES
+ *   Defines data types for XMLRPC_VECTOR.
+ *   Deprecated for public use.  See XMLRPC_VALUE_TYPE_EASY
+ * SEE ALSO
+ *   XMLRPC_VALUE_TYPE
+ *   XMLRPC_REQUEST_TYPE
+ * SOURCE
+ */
+typedef enum _XMLRPC_VECTOR_TYPE {
+   xmlrpc_vector_none,            /* not an array                   */
+   xmlrpc_vector_array,           /* no values may have key names   */
+   xmlrpc_vector_mixed,           /* some values may have key names */
+   xmlrpc_vector_struct           /* all values must have key names */
+} XMLRPC_VECTOR_TYPE;
+/*******/
+
+/****d* VALUE/XMLRPC_VALUE_TYPE_EASY
+ * NAME
+ *   XMLRPC_VALUE_TYPE_EASY
+ * NOTES
+ *   Defines data types for XMLRPC_VALUE, including vector types.
+ * SEE ALSO
+ *   XMLRPC_VECTOR_TYPE
+ *   XMLRPC_REQUEST_TYPE
+ * SOURCE
+ */
+typedef enum _XMLRPC_VALUE_TYPE_EASY {
+   xmlrpc_type_none,               /* not a value                    */
+   xmlrpc_type_empty,              /* empty value, eg NULL           */
+   xmlrpc_type_base64,             /* base64 value, eg binary data   */
+   xmlrpc_type_boolean,            /* boolean  [0 | 1]               */
+   xmlrpc_type_datetime,           /* datetime [ISO8601 | time_t]    */
+   xmlrpc_type_double,             /* double / floating point        */
+   xmlrpc_type_int,                /* integer                        */
+   xmlrpc_type_string,             /* string                         */
+/* -- IMPORTANT: identical to XMLRPC_VALUE_TYPE to this point. --   */
+       xmlrpc_type_array,              /* vector array                   */
+       xmlrpc_type_mixed,              /* vector mixed                   */
+       xmlrpc_type_struct              /* vector struct                  */
+} XMLRPC_VALUE_TYPE_EASY;
+/*******/
+
+
+/****d* VALUE/XMLRPC_REQUEST_TYPE
+ * NAME
+ *   XMLRPC_REQUEST_TYPE
+ * NOTES
+ *   Defines data types for XMLRPC_REQUEST
+ * SEE ALSO
+ *   XMLRPC_VALUE_TYPE
+ *   XMLRPC_VECTOR_TYPE
+ * SOURCE
+ */
+typedef enum _xmlrpc_request_type {
+   xmlrpc_request_none,          /* not a valid request            */
+   xmlrpc_request_call,          /* calling/invoking a method      */
+   xmlrpc_request_response,      /* responding to a method call    */
+} XMLRPC_REQUEST_TYPE;
+/*******/
+
+/****d* VALUE/XMLRPC_ERROR_CODE
+ * NAME
+ *   XMLRPC_ERROR_CODE
+ * NOTES
+ *   All existing error codes
+ * SEE ALSO
+ *   XMLRPC_REQUEST_ERROR
+ * SOURCE
+ */
+typedef enum _xmlrpc_error_code {
+   xmlrpc_error_none                      = 0,              /* not an error                                      */
+   xmlrpc_error_parse_xml_syntax          = -32700,
+   xmlrpc_error_parse_unknown_encoding    = -32701,
+   xmlrpc_error_parse_bad_encoding        = -32702,
+   xmlrpc_error_invalid_xmlrpc            = -32600,
+   xmlrpc_error_unknown_method            = -32601,
+   xmlrpc_error_invalid_params            = -32602,
+   xmlrpc_error_internal_server           = -32603,
+   xmlrpc_error_application               = -32500,
+   xmlrpc_error_system                    = -32400,
+   xmlrpc_error_transport                 = -32300
+} XMLRPC_ERROR_CODE;
+/******/
+
+#define xmlrpc_error_parse_xml_syntax_str       "parse error. not well formed."
+#define xmlrpc_error_parse_unknown_encoding_str "parse error. unknown encoding"
+#define xmlrpc_error_parse_bad_encoding_str     "parse error. invalid character for encoding"
+#define xmlrpc_error_invalid_xmlrpc_str         "server error. xml-rpc not conforming to spec"
+#define xmlrpc_error_unknown_method_str         "server error. method not found."
+#define xmlrpc_error_invalid_params_str         "server error. invalid method parameters"
+#define xmlrpc_error_internal_server_str        "server error. internal xmlrpc library error"
+#define xmlrpc_error_application_str            "application error."
+#define xmlrpc_error_system_str                 "system error."
+#define xmlrpc_error_transport_str              "transport error."
+
+
+
+/****d* VALUE/XMLRPC_VERSION
+ * NAME
+ *   XMLRPC_VERSION
+ * NOTES
+ *   Defines xml vocabulary used for generated xml
+ * SEE ALSO
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS
+ *   XMLRPC_REQUEST_To_XML ()
+ * SOURCE
+ */
+typedef enum _xmlrpc_version {
+   xmlrpc_version_none = 0,      /* not a recognized vocabulary    */ 
+   xmlrpc_version_1_0 = 1,       /* xmlrpc 1.0 standard vocab      */ 
+   xmlrpc_version_simple = 2,    /* alt more readable vocab        */ 
+   xmlrpc_version_danda = 2,     /* same as simple. legacy         */
+       xmlrpc_version_soap_1_1 = 3     /* SOAP. version 1.1              */
+} XMLRPC_VERSION;
+/******/
+
+/****s* VALUE/XMLRPC_REQUEST_OUTPUT_OPTIONS
+ * NAME
+ *   XMLRPC_REQUEST_OUTPUT_OPTIONS
+ * NOTES
+ *   Defines output options for generated xml
+ * SEE ALSO
+ *   XMLRPC_VERSION
+ *   XML_ELEM_OUTPUT_OPTIONS
+ *   XMLRPC_REQUEST_To_XML ()
+ * SOURCE
+ */
+typedef struct _xmlrpc_request_output_options {
+   STRUCT_XML_ELEM_OUTPUT_OPTIONS xml_elem_opts;  /* xml_element specific output options */
+   XMLRPC_VERSION                 version;        /* xml vocabulary to use               */
+} STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS, *XMLRPC_REQUEST_OUTPUT_OPTIONS;
+/******/
+
+/****s* VALUE/XMLRPC_REQUEST_INPUT_OPTIONS
+ * NAME
+ *   XMLRPC_REQUEST_INPUT_OPTIONS
+ * NOTES
+ *   Defines options for reading in xml data
+ * SEE ALSO
+ *   XMLRPC_VERSION
+ *   XML_ELEM_INPUT_OPTIONS
+ *   XMLRPC_REQUEST_From_XML ()
+ * SOURCE
+ */
+typedef struct _xmlrpc_request_input_options {
+   STRUCT_XML_ELEM_INPUT_OPTIONS  xml_elem_opts;  /* xml_element specific output options */
+} STRUCT_XMLRPC_REQUEST_INPUT_OPTIONS, *XMLRPC_REQUEST_INPUT_OPTIONS;
+/******/
+
+/****s* VALUE/XMLRPC_ERROR
+ * NAME
+ *   XMLRPC_ERROR
+ * NOTES
+ *   For the reporting and handling of errors
+ * SOURCE
+ */
+typedef struct _xmlrpc_error {
+   XMLRPC_ERROR_CODE      code;
+   STRUCT_XML_ELEM_ERROR  xml_elem_error;  /* xml_element errors (parser errors) */
+} STRUCT_XMLRPC_ERROR, *XMLRPC_ERROR;
+/******/
+
+
+/****d* VALUE/XMLRPC_CASE_COMPARISON
+ * NAME
+ *   XMLRPC_CASE_COMPARISON
+ * NOTES
+ *   Defines case comparison options for XMLRPC_VALUE/VECTOR API's
+ * SEE ALSO
+ *   XMLRPC_CASE
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+typedef enum _xmlrpc_case_comparison {
+   xmlrpc_case_insensitive,      /* use case-insensitive compare */
+   xmlrpc_case_sensitive         /* use case-sensitive compare   */
+} XMLRPC_CASE_COMPARISON;
+/******/
+
+/****d* VALUE/XMLRPC_CASE
+ * NAME
+ *   XMLRPC_CASE
+ * NOTES
+ *   Defines case behavior when setting IDs in XMLRPC_VALUE API's
+ * SEE ALSO
+ *   XMLRPC_CASE_COMPARISON
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+typedef enum _xmlrpc_case {
+   xmlrpc_case_exact,            /* leave case alone             */
+   xmlrpc_case_lower,            /* lower-case id                */
+   xmlrpc_case_upper             /* upper-case id                */
+} XMLRPC_CASE;
+/******/
+
+/* if you don't like these defaults, you can set them with XMLRPC_SetDefaultIdCase*() */
+#define XMLRPC_DEFAULT_ID_CASE              XMLRPC_GetDefaultIdCase()
+#define XMLRPC_DEFAULT_ID_CASE_SENSITIVITY  XMLRPC_GetDefaultIdCaseComparison()
+
+/* opaque (non-public) types. defined locally in xmlrpc.c */
+typedef struct _xmlrpc_request* XMLRPC_REQUEST;
+typedef struct _xmlrpc_server* XMLRPC_SERVER;
+typedef struct _xmlrpc_value* XMLRPC_VALUE;
+
+/****d* VALUE/XMLRPC_Callback
+ * NAME
+ *   XMLRPC_Callback
+ * NOTES
+ *   Function prototype for user defined method handlers (callbacks).
+ * SEE ALSO
+ *   XMLRPC_ServerRegisterMethod ()
+ *   XMLRPC_ServerCallMethod ()
+ *   XMLRPC_REQUEST
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+typedef XMLRPC_VALUE (*XMLRPC_Callback)(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+/******/
+
+/* ID Case Defaults */
+XMLRPC_CASE XMLRPC_GetDefaultIdCase(void);
+XMLRPC_CASE XMLRPC_SetDefaultIdCase(XMLRPC_CASE id_case);
+XMLRPC_CASE_COMPARISON XMLRPC_GetDefaultIdCaseComparison(void);
+XMLRPC_CASE_COMPARISON XMLRPC_SetDefaultIdCaseComparison(XMLRPC_CASE_COMPARISON id_case);
+
+/* Vector manipulation */
+int XMLRPC_VectorSize(XMLRPC_VALUE value);
+XMLRPC_VALUE XMLRPC_VectorRewind(XMLRPC_VALUE value);
+XMLRPC_VALUE XMLRPC_VectorNext(XMLRPC_VALUE value);
+int XMLRPC_SetIsVector(XMLRPC_VALUE value, XMLRPC_VECTOR_TYPE type);
+int XMLRPC_AddValueToVector(XMLRPC_VALUE target, XMLRPC_VALUE source);
+int XMLRPC_AddValuesToVector(XMLRPC_VALUE target, ...);
+int XMLRPC_VectorRemoveValue(XMLRPC_VALUE vector, XMLRPC_VALUE value);
+XMLRPC_VALUE XMLRPC_VectorGetValueWithID_Case(XMLRPC_VALUE vector, const char* id, XMLRPC_CASE_COMPARISON id_case);
+
+
+/* Create values */
+XMLRPC_VALUE XMLRPC_CreateValueBoolean(const char* id, int truth);
+XMLRPC_VALUE XMLRPC_CreateValueBase64(const char* id, const char* s, int len);
+XMLRPC_VALUE XMLRPC_CreateValueDateTime(const char* id, time_t time);
+XMLRPC_VALUE XMLRPC_CreateValueDateTime_ISO8601(const char* id, const char *s);
+XMLRPC_VALUE XMLRPC_CreateValueDouble(const char* id, double f);
+XMLRPC_VALUE XMLRPC_CreateValueInt(const char* id, int i);
+XMLRPC_VALUE XMLRPC_CreateValueString(const char* id, const char* s, int len);
+XMLRPC_VALUE XMLRPC_CreateValueEmpty(void);
+XMLRPC_VALUE XMLRPC_CreateVector(const char* id, XMLRPC_VECTOR_TYPE type);
+
+/* Cleanup values */
+void XMLRPC_CleanupValue(XMLRPC_VALUE value);
+
+/* Request error */
+XMLRPC_VALUE XMLRPC_RequestSetError (XMLRPC_REQUEST request, XMLRPC_VALUE error);
+XMLRPC_VALUE XMLRPC_RequestGetError (XMLRPC_REQUEST request);
+
+/* Copy values */
+XMLRPC_VALUE XMLRPC_CopyValue(XMLRPC_VALUE value);
+XMLRPC_VALUE XMLRPC_DupValueNew(XMLRPC_VALUE xSource);
+
+/* Set Values */
+void XMLRPC_SetValueDateTime(XMLRPC_VALUE value, time_t time);
+void XMLRPC_SetValueDateTime_ISO8601(XMLRPC_VALUE value, const char* s);
+void XMLRPC_SetValueDouble(XMLRPC_VALUE value, double val);
+void XMLRPC_SetValueInt(XMLRPC_VALUE value, int val);
+void XMLRPC_SetValueBoolean(XMLRPC_VALUE value, int val);
+const char *XMLRPC_SetValueString(XMLRPC_VALUE value, const char* s, int len);
+void XMLRPC_SetValueBase64(XMLRPC_VALUE value, const char* s, int len);
+const char *XMLRPC_SetValueID_Case(XMLRPC_VALUE value, const char* id, int len, XMLRPC_CASE id_case);
+#define XMLRPC_SetValueID(value, id, len) XMLRPC_SetValueID_Case(value, id, len, XMLRPC_DEFAULT_ID_CASE)
+
+/* Get Values */
+const char* XMLRPC_GetValueString(XMLRPC_VALUE value);
+int XMLRPC_GetValueStringLen(XMLRPC_VALUE value);
+int XMLRPC_GetValueInt(XMLRPC_VALUE value);
+int XMLRPC_GetValueBoolean(XMLRPC_VALUE value);
+double XMLRPC_GetValueDouble(XMLRPC_VALUE value);
+const char* XMLRPC_GetValueBase64(XMLRPC_VALUE value);
+time_t XMLRPC_GetValueDateTime(XMLRPC_VALUE value);
+const char* XMLRPC_GetValueDateTime_ISO8601(XMLRPC_VALUE value);
+const char* XMLRPC_GetValueID(XMLRPC_VALUE value);
+
+/* Type introspection */
+XMLRPC_VALUE_TYPE XMLRPC_GetValueType(XMLRPC_VALUE v);
+XMLRPC_VALUE_TYPE_EASY XMLRPC_GetValueTypeEasy(XMLRPC_VALUE v);
+XMLRPC_VECTOR_TYPE XMLRPC_GetVectorType(XMLRPC_VALUE v);
+
+/* Parsing and Creating XML */
+XMLRPC_REQUEST XMLRPC_REQUEST_FromXML(const char* in_buf, int len, XMLRPC_REQUEST_INPUT_OPTIONS in_options);
+XMLRPC_VALUE XMLRPC_VALUE_FromXML(const char* in_buf, int len, XMLRPC_REQUEST_INPUT_OPTIONS in_options);
+char* XMLRPC_REQUEST_ToXML(XMLRPC_REQUEST request, int *buf_len);
+char* XMLRPC_VALUE_ToXML(XMLRPC_VALUE val, int* buf_len);
+
+/* Request manipulation funcs */
+const char* XMLRPC_RequestSetMethodName(XMLRPC_REQUEST request, const char* methodName);
+const char* XMLRPC_RequestGetMethodName(XMLRPC_REQUEST request);
+XMLRPC_REQUEST XMLRPC_RequestNew(void);
+void XMLRPC_RequestFree(XMLRPC_REQUEST request, int bFreeIO);
+XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestSetOutputOptions(XMLRPC_REQUEST request, XMLRPC_REQUEST_OUTPUT_OPTIONS output);
+XMLRPC_REQUEST_OUTPUT_OPTIONS XMLRPC_RequestGetOutputOptions(XMLRPC_REQUEST request);
+XMLRPC_VALUE XMLRPC_RequestSetData(XMLRPC_REQUEST request, XMLRPC_VALUE data);
+XMLRPC_VALUE XMLRPC_RequestGetData(XMLRPC_REQUEST request);
+XMLRPC_REQUEST_TYPE XMLRPC_RequestSetRequestType(XMLRPC_REQUEST request, XMLRPC_REQUEST_TYPE type);
+XMLRPC_REQUEST_TYPE XMLRPC_RequestGetRequestType(XMLRPC_REQUEST request);
+
+/* Server Creation/Destruction; Method Registration and Invocation */
+XMLRPC_SERVER XMLRPC_ServerCreate(void);
+XMLRPC_SERVER XMLRPC_GetGlobalServer(void);   /* better to use XMLRPC_ServerCreate if you can */
+void XMLRPC_ServerDestroy(XMLRPC_SERVER server);
+int XMLRPC_ServerRegisterMethod(XMLRPC_SERVER server, const char *name, XMLRPC_Callback cb);
+XMLRPC_Callback XMLRPC_ServerFindMethod(XMLRPC_SERVER server, const char* callName);
+XMLRPC_VALUE XMLRPC_ServerCallMethod(XMLRPC_SERVER server, XMLRPC_REQUEST request, void* userData);
+
+#include "xmlrpc_introspection.h"
+
+/* Fault interrogation funcs */
+int XMLRPC_ValueIsFault (XMLRPC_VALUE value);
+int XMLRPC_ResponseIsFault(XMLRPC_REQUEST response);
+int XMLRPC_GetValueFaultCode (XMLRPC_VALUE value);
+int XMLRPC_GetResponseFaultCode(XMLRPC_REQUEST response);
+const char* XMLRPC_GetValueFaultString (XMLRPC_VALUE value);
+const char* XMLRPC_GetResponseFaultString (XMLRPC_REQUEST response);
+
+
+/* Public Utility funcs */
+XMLRPC_VALUE XMLRPC_UtilityCreateFault(int fault_code, const char* fault_string);
+void XMLRPC_Free(void* mem);
+const char*  XMLRPC_GetVersionString(void);
+
+/****d* VALUE/XMLRPC_MACROS
+ * NAME
+ *   Some Helpful Macros
+ * NOTES
+ *   Some macros for making life easier.  Should be self-explanatory.
+ * SEE ALSO
+ *   XMLRPC_AddValueToVector ()
+ *   XMLRPC_VectorGetValueWithID_Case ()
+ *   XMLRPC_VALUE
+ * SOURCE
+ */
+
+/* Append values to vector */
+#define XMLRPC_VectorAppendString(vector, id, s, len) XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueString(id, s, len))
+#define XMLRPC_VectorAppendBase64(vector, id, s, len) XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueBase64(id, s, len))
+#define XMLRPC_VectorAppendDateTime(vector, id, time) XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDateTime(id, time))
+#define XMLRPC_VectorAppendDateTime_ISO8601(vector, id, s) XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDateTime_ISO8601(id, s))
+#define XMLRPC_VectorAppendDouble(vector, id, f) XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueDouble(id, f))
+#define XMLRPC_VectorAppendInt(vector, id, i) XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueInt(id, i))
+#define XMLRPC_VectorAppendBoolean(vector, id, i) XMLRPC_AddValueToVector(vector, XMLRPC_CreateValueBoolean(id, i))
+
+/* Get named values from vector */
+#define XMLRPC_VectorGetValueWithID(vector, id) XMLRPC_VectorGetValueWithID_Case(vector, id, XMLRPC_DEFAULT_ID_CASE_SENSITIVITY)
+#define XMLRPC_VectorGetStringWithID(vector, id) XMLRPC_GetValueString(XMLRPC_VectorGetValueWithID(vector, id))
+#define XMLRPC_VectorGetBase64WithID(vector, id) XMLRPC_GetValueBase64(XMLRPC_VectorGetValueWithID(vector, id))
+#define XMLRPC_VectorGetDateTimeWithID(vector, id) XMLRPC_GetValueDateTime(XMLRPC_VectorGetValueWithID(vector, id))
+#define XMLRPC_VectorGetDoubleWithID(vector, id) XMLRPC_GetValueDouble(XMLRPC_VectorGetValueWithID(vector, id))
+#define XMLRPC_VectorGetIntWithID(vector, id) XMLRPC_GetValueInt(XMLRPC_VectorGetValueWithID(vector, id))
+#define XMLRPC_VectorGetBooleanWithID(vector, id) XMLRPC_GetValueBoolean(XMLRPC_VectorGetValueWithID(vector, id))
+
+/******/
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* not XMLRPC_ALREADY_INCLUDED */
+
+
+
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc.m4 b/php/xmlrpc/libxmlrpc/xmlrpc.m4
new file mode 100644 (file)
index 0000000..87da92d
--- /dev/null
@@ -0,0 +1,12 @@
+AC_DEFUN([XMLRPC_CHECKS],[     
+
+AC_REQUIRE([AC_PROG_CC])
+AC_REQUIRE([AC_PROG_LN_S])
+AC_REQUIRE([AC_PROG_RANLIB])
+
+AC_DEFINE(UNDEF_THREADS_HACK,,[ ])
+
+XMLRPC_HEADER_CHECKS
+XMLRPC_TYPE_CHECKS
+XMLRPC_FUNCTION_CHECKS
+])
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc_introspection.c b/php/xmlrpc/libxmlrpc/xmlrpc_introspection.c
new file mode 100644 (file)
index 0000000..589ff8e
--- /dev/null
@@ -0,0 +1,604 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+
+/****h* ABOUT/xmlrpc_introspection
+ * AUTHOR
+ *   Dan Libby, aka danda  (dan@libby.com)
+ * HISTORY
+ *   $Log: xmlrpc_introspection.c,v $
+ *   Revision 1.4  2003/12/16 21:00:21  sniper
+ *   Fix some compile warnings (patch by Joe Orton)
+ *
+ *   Revision 1.3  2002/07/05 04:43:53  danda
+ *   merged in updates from SF project.  bring php repository up to date with xmlrpc-epi version 0.51
+ *
+ *   Revision 1.9  2001/09/29 21:58:05  danda
+ *   adding cvs log to history section
+ *
+ *   4/10/2001 -- danda -- initial introspection support
+ * TODO
+ * NOTES
+ *******/
+
+
+#ifdef _WIN32
+#include "xmlrpc_win32.h"
+#endif
+#include "queue.h"
+#include "xmlrpc.h"
+#include "xmlrpc_private.h"
+#include "xmlrpc_introspection_private.h"
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+
+/* forward declarations for static (non public, non api) funcs */
+static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData);
+
+
+/*-**********************************
+* Introspection Callbacks (methods) *
+************************************/
+
+/* iterates through a list of structs and finds the one with key "name" matching
+ * needle.  slow, would benefit from a struct key hash.
+ */
+inline XMLRPC_VALUE find_named_value(XMLRPC_VALUE list, const char* needle) {
+   XMLRPC_VALUE xIter = XMLRPC_VectorRewind(list);
+   while(xIter) {
+      const char* name = XMLRPC_VectorGetStringWithID(xIter, xi_token_name);
+      if(name && !strcmp(name, needle)) {
+         return xIter;
+      }
+      xIter = XMLRPC_VectorNext(list);
+   }
+   return NULL;
+}
+
+
+/* iterates through docs callbacks and calls any that have not yet been called */
+static void check_docs_loaded(XMLRPC_SERVER server, void* userData) {
+   if(server) {
+      q_iter qi = Q_Iter_Head_F(&server->docslist);
+      while( qi ) {
+         doc_method* dm = Q_Iter_Get_F(qi);
+         if(dm && !dm->b_called) {
+            dm->method(server, userData);
+            dm->b_called = 1;
+         }
+         qi = Q_Iter_Next_F(qi);
+      }
+   }
+}
+
+
+/* utility function for xi_system_describe_methods_cb */
+inline void describe_method(XMLRPC_SERVER server, XMLRPC_VALUE vector, const char* method) {
+   if(method) {
+      server_method* sm = find_method(server, method);
+      if(sm) {
+         XMLRPC_AddValueToVector(vector, sm->desc);
+      }
+   }
+}
+
+
+
+/* system.describeMethods() callback */
+static XMLRPC_VALUE xi_system_describe_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   XMLRPC_VALUE xParams = XMLRPC_VectorRewind(XMLRPC_RequestGetData(input));
+   XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+   XMLRPC_VALUE xMethodList = XMLRPC_CreateVector("methodList", xmlrpc_vector_array);
+   XMLRPC_VALUE xTypeList = NULL;
+   int bAll = 1;
+
+   /* lazy loading of introspection data */
+   check_docs_loaded(server, userData);
+
+   xTypeList = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
+
+   XMLRPC_AddValueToVector(xResponse, xTypeList);
+   XMLRPC_AddValueToVector(xResponse, xMethodList);
+
+   /* check if we have any param */
+   if(xParams) {
+      /* check if string or vector (1 or n) */
+      XMLRPC_VALUE_TYPE type = XMLRPC_GetValueType(xParams);
+      if(type == xmlrpc_string) {
+         /* just one.  spit it out. */
+         describe_method(server, xMethodList, XMLRPC_GetValueString(xParams));
+         bAll = 0;
+      }
+      else if(type == xmlrpc_vector) {
+         /* multiple.  spit all out */
+         XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xParams);
+         while(xIter) {
+            describe_method(server, xMethodList, XMLRPC_GetValueString(xIter));
+            xIter = XMLRPC_VectorNext(xParams);
+         }
+         bAll = 0;
+      }
+   }
+
+   /* otherwise, default to sending all methods */
+   if(bAll) {
+      q_iter qi = Q_Iter_Head_F(&server->methodlist);
+      while( qi ) {
+         server_method* sm = Q_Iter_Get_F(qi);
+         if(sm) {
+            XMLRPC_AddValueToVector(xMethodList, sm->desc);
+         }
+         qi = Q_Iter_Next_F(qi);
+      }
+   }
+   
+   return xResponse;
+}
+
+/* this complies with system.listMethods as defined at http://xmlrpc.usefulinc.com/doc/reserved.html */
+static XMLRPC_VALUE xi_system_list_methods_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   XMLRPC_VALUE xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
+
+   q_iter qi = Q_Iter_Head_F(&server->methodlist);
+   while( qi ) {
+      server_method* sm = Q_Iter_Get_F(qi);
+      if(sm) {
+         XMLRPC_VectorAppendString(xResponse, 0, sm->name, 0);
+      }
+      qi = Q_Iter_Next_F(qi);
+   }
+   return xResponse;
+}
+
+/* this complies with system.methodSignature as defined at 
+ * http://xmlrpc.usefulinc.com/doc/sysmethodsig.html 
+ */
+static XMLRPC_VALUE xi_system_method_signature_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
+   XMLRPC_VALUE xResponse = NULL;
+
+   /* lazy loading of introspection data */
+   check_docs_loaded(server, userData);
+
+   if(method) {
+      server_method* sm = find_method(server, method);
+      if(sm && sm->desc) {
+         XMLRPC_VALUE xTypesArray = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
+         XMLRPC_VALUE xIter, xParams, xSig, xSigIter;
+         const char* type;
+
+         /* array of possible signatures.  */
+         xResponse = XMLRPC_CreateVector(NULL, xmlrpc_vector_array);
+
+         /* find first signature */
+         xSig = XMLRPC_VectorGetValueWithID(sm->desc, xi_token_signatures);
+         xSigIter = XMLRPC_VectorRewind( xSig );
+
+         /* iterate through sigs */
+         while(xSigIter) {
+            /* first type is the return value */
+            type = XMLRPC_VectorGetStringWithID(XMLRPC_VectorRewind(
+                                                 XMLRPC_VectorGetValueWithID(xSigIter, xi_token_returns)), 
+                                                xi_token_type);
+            XMLRPC_AddValueToVector(xTypesArray, 
+                                    XMLRPC_CreateValueString(NULL, 
+                                                             type ? type : type_to_str(xmlrpc_none, 0), 
+                                    0));
+
+            /* the rest are parameters */
+            xParams = XMLRPC_VectorGetValueWithID(xSigIter, xi_token_params);
+            xIter = XMLRPC_VectorRewind(xParams);
+
+            /* iter through params, adding to types array */
+            while(xIter) {
+               XMLRPC_AddValueToVector(xTypesArray,
+                                       XMLRPC_CreateValueString(NULL, 
+                                                                XMLRPC_VectorGetStringWithID(xIter, xi_token_type),
+                                                                0));
+               xIter = XMLRPC_VectorNext(xParams);
+            }
+
+            /* add types for this signature */
+            XMLRPC_AddValueToVector(xResponse, xTypesArray);
+
+            xSigIter = XMLRPC_VectorNext( xSig );
+         }
+      }
+   }
+
+   return xResponse;
+}
+
+/* this complies with system.methodHelp as defined at 
+ * http://xmlrpc.usefulinc.com/doc/sysmethhelp.html 
+ */
+static XMLRPC_VALUE xi_system_method_help_cb(XMLRPC_SERVER server, XMLRPC_REQUEST input, void* userData) {
+   const char* method = XMLRPC_GetValueString(XMLRPC_VectorRewind(XMLRPC_RequestGetData(input)));
+   XMLRPC_VALUE xResponse = NULL;
+
+   /* lazy loading of introspection data */
+   check_docs_loaded(server, userData);
+
+   if(method) {
+      server_method* sm = find_method(server, method);
+      if(sm && sm->desc) {
+         const char* help = XMLRPC_VectorGetStringWithID(sm->desc, xi_token_purpose);
+
+         /* returns a documentation string, or empty string */
+         xResponse = XMLRPC_CreateValueString(NULL, help ? help : xi_token_empty, 0);
+      }
+   }
+
+   return xResponse;
+}
+
+/*-**************************************
+* End Introspection Callbacks (methods) *
+****************************************/
+
+
+/*-************************
+* Introspection Utilities *
+**************************/
+
+/* performs registration of introspection methods */
+void xi_register_system_methods(XMLRPC_SERVER server) {
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_list_methods, xi_system_list_methods_cb);
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_method_help, xi_system_method_help_cb);
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_method_signature, xi_system_method_signature_cb);
+   XMLRPC_ServerRegisterMethod(server, xi_token_system_describe_methods, xi_system_describe_methods_cb);
+}
+
+/* describe a value (param, return, type) */
+static XMLRPC_VALUE describeValue_worker(const char* type, const char* id, const char* desc, int optional, const char* default_val, XMLRPC_VALUE sub_params) {
+   XMLRPC_VALUE xParam = NULL;
+   if(id || desc) {
+      xParam = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+      XMLRPC_VectorAppendString(xParam, xi_token_name, id, 0);
+      XMLRPC_VectorAppendString(xParam, xi_token_type, type, 0);
+      XMLRPC_VectorAppendString(xParam, xi_token_description, desc, 0);
+      if(optional != 2) {
+         XMLRPC_VectorAppendInt(xParam, xi_token_optional, optional);
+      }
+      if(optional == 1 && default_val) {
+         XMLRPC_VectorAppendString(xParam, xi_token_default, default_val, 0);
+      }
+      XMLRPC_AddValueToVector(xParam, sub_params);
+   }
+   return xParam;
+}
+
+
+/* convert an xml tree conforming to spec <url tbd> to  XMLRPC_VALUE
+ * suitable for use with XMLRPC_ServerAddIntrospectionData
+ */
+XMLRPC_VALUE xml_element_to_method_description(xml_element* el, XMLRPC_ERROR err) {
+   XMLRPC_VALUE xReturn = NULL;
+
+   if(el->name) {
+      const char* name = NULL;
+      const char* type = NULL;
+      const char* basetype = NULL;
+      const char* desc = NULL;
+      const char* def = NULL;
+      int optional = 0;
+      xml_element_attr* attr_iter = Q_Head(&el->attrs);
+
+      /* grab element attributes up front to save redundant while loops */
+      while(attr_iter) {
+         if(!strcmp(attr_iter->key, "name")) {
+            name = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "type")) {
+            type = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "basetype")) {
+            basetype = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "desc")) {
+            desc = attr_iter->val;
+         }
+         else if(!strcmp(attr_iter->key, "optional")) {
+            if(attr_iter->val && !strcmp(attr_iter->val, "yes")) {
+               optional = 1;
+            }
+         }
+         else if(!strcmp(attr_iter->key, "default")) {
+            def = attr_iter->val;
+         }
+         attr_iter = Q_Next(&el->attrs);
+      }
+
+      /* value and typeDescription behave about the same */
+      if(!strcmp(el->name, "value") || !strcmp(el->name, "typeDescription")) {
+         XMLRPC_VALUE xSubList = NULL;
+         const char* ptype = !strcmp(el->name, "value") ? type : basetype;
+         if(ptype) {
+            if(Q_Size(&el->children) &&
+               (!strcmp(ptype, "array") || !strcmp(ptype, "struct") || !strcmp(ptype, "mixed"))) {
+               xSubList = XMLRPC_CreateVector("member", xmlrpc_vector_array);
+
+               if(xSubList) {
+                  xml_element* elem_iter = Q_Head(&el->children);
+                  while(elem_iter) {
+                     XMLRPC_AddValueToVector(xSubList, 
+                                             xml_element_to_method_description(elem_iter, err));
+                     elem_iter = Q_Next(&el->children);
+                  }
+               }
+            }
+            xReturn = describeValue_worker(ptype, name, (desc ? desc : (xSubList ? NULL : el->text.str)), optional, def, xSubList);
+         }
+      }
+
+      /* these three kids are about equivalent */
+      else if(!strcmp(el->name, "params") || 
+              !strcmp(el->name, "returns") || 
+              !strcmp(el->name, "signature")) {
+         if(Q_Size(&el->children)) {
+            xml_element* elem_iter = Q_Head(&el->children);
+            xReturn = XMLRPC_CreateVector(!strcmp(el->name, "signature") ? NULL : el->name, xmlrpc_vector_struct);
+
+
+            while(elem_iter) {
+               XMLRPC_AddValueToVector(xReturn, 
+                                       xml_element_to_method_description(elem_iter, err));
+               elem_iter = Q_Next(&el->children);
+            }
+         }
+      }
+
+
+      else if(!strcmp(el->name, "methodDescription")) {
+         xml_element* elem_iter = Q_Head(&el->children);
+         xReturn = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+
+         XMLRPC_VectorAppendString(xReturn, xi_token_name, name, 0);
+
+         while(elem_iter) {
+            XMLRPC_AddValueToVector(xReturn, 
+                                    xml_element_to_method_description(elem_iter, err));
+            elem_iter = Q_Next(&el->children);
+         }
+      }
+
+      /* items are slightly special */
+      else if(!strcmp(el->name, "item")) {
+         xReturn = XMLRPC_CreateValueString(name, el->text.str, el->text.len);
+      }
+
+      /* sure.  we'll let any ol element with children through */
+      else if(Q_Size(&el->children)) {
+         xml_element* elem_iter = Q_Head(&el->children);
+         xReturn = XMLRPC_CreateVector(el->name, xmlrpc_vector_mixed);
+
+         while(elem_iter) {
+            XMLRPC_AddValueToVector(xReturn, 
+                                    xml_element_to_method_description(elem_iter, err));
+            elem_iter = Q_Next(&el->children);
+         }
+      }
+
+      /* or anything at all really, so long as its got some text. 
+       * no reason being all snotty about a spec, right? 
+       */
+      else if(el->name && el->text.len) {
+         xReturn = XMLRPC_CreateValueString(el->name, el->text.str, el->text.len);
+      }
+   }
+
+   return xReturn;
+}
+
+/*-****************************
+* End Introspection Utilities *
+******************************/
+
+
+
+/*-******************
+* Introspection API *
+********************/
+
+
+/****f* VALUE/XMLRPC_IntrospectionCreateDescription
+ * NAME
+ *   XMLRPC_IntrospectionCreateDescription
+ * SYNOPSIS
+ *   XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err)
+ * FUNCTION
+ *   converts raw xml describing types and methods into an
+ *   XMLRPC_VALUE suitable for use with XMLRPC_ServerAddIntrospectionData()
+ * INPUTS
+ *   xml - xml data conforming to introspection spec at <url tbd>
+ *   err - optional pointer to error struct. filled in if error occurs and not NULL.
+ * RESULT
+ *   XMLRPC_VALUE - newly created value, or NULL if fatal error.
+ * BUGS
+ *   Currently does little or no validation of xml.
+ *   Only parse errors are currently reported in err, not structural errors.
+ * SEE ALSO
+ *   XMLRPC_ServerAddIntrospectionData ()
+ * SOURCE
+ */
+XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR err) {
+   XMLRPC_VALUE xReturn = NULL;
+   xml_element* root = xml_elem_parse_buf(xml, 0, 0, err ? &err->xml_elem_error : NULL);
+
+   if(root) {
+      xReturn = xml_element_to_method_description(root, err);
+
+      xml_elem_free(root);
+   }
+
+   return xReturn;
+
+}
+/*******/
+
+
+/****f* SERVER/XMLRPC_ServerAddIntrospectionData
+ * NAME
+ *   XMLRPC_ServerAddIntrospectionData
+ * SYNOPSIS
+ *   int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc)
+ * FUNCTION
+ *   updates server with additional introspection data
+ * INPUTS
+ *   server - target server
+ *   desc - introspection data, should be a struct generated by 
+ *          XMLRPC_IntrospectionCreateDescription ()
+ * RESULT
+ *   int - 1 if success, else 0
+ * NOTES
+ *  - function will fail if neither typeList nor methodList key is present in struct.
+ *  - if method or type already exists, it will be replaced.
+ *  - desc is never freed by the server.  caller is responsible for cleanup.
+ * BUGS
+ *   - horribly slow lookups. prime candidate for hash improvements.
+ *   - uglier and more complex than I like to see for API functions.
+ * SEE ALSO
+ *   XMLRPC_ServerAddIntrospectionData ()
+ *   XMLRPC_ServerRegisterIntrospectionCallback ()
+ *   XMLRPC_CleanupValue ()
+ * SOURCE
+ */
+int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc) {
+   int bSuccess = 0;
+   if(server && desc) {
+      XMLRPC_VALUE xNewTypes = XMLRPC_VectorGetValueWithID(desc, "typeList");
+      XMLRPC_VALUE xNewMethods = XMLRPC_VectorGetValueWithID(desc, "methodList");
+      XMLRPC_VALUE xServerTypes = XMLRPC_VectorGetValueWithID(server->xIntrospection, "typeList");
+
+      if(xNewMethods) {
+         XMLRPC_VALUE xMethod = XMLRPC_VectorRewind(xNewMethods);
+
+         while(xMethod) {
+            const char* name = XMLRPC_VectorGetStringWithID(xMethod, xi_token_name);
+            server_method* sm = find_method(server, name);
+
+            if(sm) {
+               if(sm->desc) {
+                  XMLRPC_CleanupValue(sm->desc);
+               }
+               sm->desc = XMLRPC_CopyValue(xMethod);
+               bSuccess = 1;
+            }
+
+            xMethod = XMLRPC_VectorNext(xNewMethods);
+         }
+      }
+      if(xNewTypes) {
+         if(!xServerTypes) {
+            if(!server->xIntrospection) {
+               server->xIntrospection = XMLRPC_CreateVector(NULL, xmlrpc_vector_struct);
+            }
+
+            XMLRPC_AddValueToVector(server->xIntrospection, xNewTypes);
+            bSuccess = 1;
+         }
+         else {
+            XMLRPC_VALUE xIter = XMLRPC_VectorRewind(xNewTypes);
+            while(xIter) {
+               /* get rid of old values */
+               XMLRPC_VALUE xPrev = find_named_value(xServerTypes, XMLRPC_VectorGetStringWithID(xIter, xi_token_name));
+               if(xPrev) {
+                  XMLRPC_VectorRemoveValue(xServerTypes, xPrev);
+               }
+               XMLRPC_AddValueToVector(xServerTypes, xIter);
+               bSuccess = 1;
+               xIter = XMLRPC_VectorNext(xNewTypes);
+            }
+         }
+      }
+   }
+   return bSuccess;
+}
+/*******/
+
+
+/****f* SERVER/XMLRPC_ServerRegisterIntrospectionCallback
+ * NAME
+ *   XMLRPC_ServerRegisterIntrospectionCallback
+ * SYNOPSIS
+ *   int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb)
+ * FUNCTION
+ *   registers a callback for lazy generation of introspection data
+ * INPUTS
+ *   server - target server
+ *   cb - callback that will generate introspection data
+ * RESULT
+ *   int - 1 if success, else 0
+ * NOTES
+ *   parsing xml and generating introspection data is fairly expensive, thus a
+ *   server may wish to wait until this data is actually requested before generating
+ *   it. Any number of callbacks may be registered at any time.  A given callback
+ *   will only ever be called once, the first time an introspection request is
+ *   processed after the time of callback registration.
+ * SEE ALSO
+ *   XMLRPC_ServerAddIntrospectionData ()
+ *   XMLRPC_IntrospectionCreateDescription ()
+ * SOURCE
+ */
+int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb) {
+   int bSuccess = 0;
+   if(server && cb) {
+
+      doc_method* dm = calloc(1, sizeof(doc_method));
+      
+      if(dm) {
+         dm->method = cb;
+         dm->b_called = 0;
+
+         if(Q_PushTail(&server->docslist, dm)) {
+            bSuccess = 1;
+         }
+         else {
+            my_free(dm);
+         }
+      }
+   }
+   return 0;
+}
+/*******/
+
+/*-**********************
+* End Introspection API *
+************************/
+
+
+
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc_introspection.h b/php/xmlrpc/libxmlrpc/xmlrpc_introspection.h
new file mode 100644 (file)
index 0000000..656e441
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+/* IMPORTANT!
+ *
+ * only public (official API) things should be in this file. Anything else
+ * should go in <group>_private.h, or in the appropriate .c file.
+ */
+
+
+#ifndef __XI_INTROSPECTION_H
+/*
+ * Avoid include redundancy.
+ */
+#define __XI_INTROSPECTION_H
+
+/*----------------------------------------------------------------------------
+ * xmlrpc_introspection.h
+ *
+ * Purpose:
+ *   define public introspection API
+ * Comments:
+ */
+
+/*----------------------------------------------------------------------------
+ * Constants
+ */
+ #define xi_token_params "params"
+ #define xi_token_returns "returns"
+ #define xi_token_related "related"
+ #define xi_token_sub "sub"
+/*----------------------------------------------------------------------------
+ * Includes
+ */
+
+/*----------------------------------------------------------------------------
+ * Structures
+ */
+ /****d* VALUE/XMLRPC_IntrospectionCallback
+ * NAME
+ *   XMLRPC_IntrospectionCallback
+ * NOTES
+ *   Function prototype for lazy documentation generation (not generated until requested).
+ * SOURCE
+ */
+typedef void (*XMLRPC_IntrospectionCallback)(XMLRPC_SERVER server, void* userData);
+/******/
+/*----------------------------------------------------------------------------
+ * Globals
+ */
+
+/*----------------------------------------------------------------------------
+ * Functions
+ */
+XMLRPC_VALUE XMLRPC_IntrospectionCreateDescription(const char* xml, XMLRPC_ERROR error);
+int XMLRPC_ServerAddIntrospectionData(XMLRPC_SERVER server, XMLRPC_VALUE desc);
+int XMLRPC_ServerRegisterIntrospectionCallback(XMLRPC_SERVER server, XMLRPC_IntrospectionCallback cb);
+/*----------------------------------------------------------------------------
+ * Macros
+ */
+
+
+#endif /* __XI_INTROSPECTION_H */
+
+
+
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc_introspection_private.h b/php/xmlrpc/libxmlrpc/xmlrpc_introspection_private.h
new file mode 100644 (file)
index 0000000..7b97fa7
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Dan Libby, Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+/* IMPORTANT!
+ *
+ * only non-public things should be in this file.  It is fine for any .c file
+ * in xmlrpc/src to include it, but users of the public API should never
+ * include it, and thus *.h files that are part of the public API should
+ * never include it, or they would break if this file is not present.
+ */
+
+
+#ifndef __XI_INTROSPECTION_PRIVATE_H
+/*
+ * Avoid include redundancy.
+ */
+#define __XI_INTROSPECTION_PRIVATE_H
+
+/*----------------------------------------------------------------------------
+ * xmlrpc_introspection_private.h
+ *
+ * Purpose:
+ *   define non-public introspection routines
+ * Comments:
+ */
+
+/*----------------------------------------------------------------------------
+ * Constants
+ */
+#define xi_token_default                    "default"
+#define xi_token_description                "description"
+#define xi_token_name                       "name"
+#define xi_token_optional                   "optional"
+#define xi_token_params                     "params"
+#define xi_token_purpose                    "purpose"
+#define xi_token_returns                    "returns"
+#define xi_token_signatures                 "signatures"
+#define xi_token_type                       "type"
+#define xi_token_version                    "version"
+#define xi_token_empty                      ""
+#define xi_token_system_describe_methods    "system.describeMethods"
+#define xi_token_system_list_methods        "system.listMethods"
+#define xi_token_system_method_help         "system.methodHelp"
+#define xi_token_system_method_signature    "system.methodSignature"
+
+/*----------------------------------------------------------------------------
+ * Includes
+ */
+
+/*----------------------------------------------------------------------------
+ * Structures
+ */
+typedef struct _doc_method {
+   XMLRPC_IntrospectionCallback         method;
+   int                                  b_called;
+} doc_method; 
+/*----------------------------------------------------------------------------
+ * Globals
+ */
+
+/*----------------------------------------------------------------------------
+ * Functions
+ */
+void xi_register_system_methods(XMLRPC_SERVER server);
+/*----------------------------------------------------------------------------
+ * Macros
+ */
+
+#endif /* __XI_INTROSPECTION_PRIVATE_H */
+
+
+
+
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc_private.h b/php/xmlrpc/libxmlrpc/xmlrpc_private.h
new file mode 100644 (file)
index 0000000..65c6b13
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+  This file is part of libXMLRPC - a C library for xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2000 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+/* only non-public things should be in this file.  It is fine for any .c file
+ * in xmlrpc/src to include it, but users of the public API should never
+ * include it, and thus *.h files that are part of the public API should
+ * never include it, or they would break if this file is not present.
+ */
+
+#ifndef XMLRPC_PRIVATE_ALREADY_INCLUDED
+/*
+ * Avoid include redundancy.
+ */
+#define XMLRPC_PRIVATE_ALREADY_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*----------------------------------------------------------------------------
+ * xmlrpc_private.h
+ *
+ * Purpose:
+ *   define non-public intra-library routines & data
+ * Comments:
+ */
+
+/*----------------------------------------------------------------------------
+ * Constants
+ */
+
+
+/*----------------------------------------------------------------------------
+ * Includes
+ */
+
+/*----------------------------------------------------------------------------
+ * Structures
+ */
+/* Some of these are typedef'd in xmlrpc.h for public use */
+
+typedef struct _xmlrpc_vector* XMLRPC_VECTOR;
+
+/****s* VALUE/XMLRPC_VALUE
+ * NAME
+ *   XMLRPC_VALUE
+ * NOTES
+ *   A value of variable data type. The most important object in this API.  :)
+ *
+ *  This struct is opaque to callers and should be accessed only via accessor functions.
+ * SEE ALSO
+ *   XMLRPC_REQUEST
+ *   XMLRPC_CreateValueEmpty ()
+ *   XMLRPC_CleanupValue ()
+ * SOURCE
+ */
+typedef struct _xmlrpc_value {
+   XMLRPC_VALUE_TYPE type; /* data type of this value                        */
+   XMLRPC_VECTOR v;        /* vector type specific info                      */
+   simplestring str;       /* string value buffer                            */
+   simplestring id;        /* id of this value.  possibly empty.             */
+   int i;                  /* integer value.                                 */
+   double d;               /* double value                                   */
+   int iRefCount;          /* So we know when we can delete the value      . */
+} STRUCT_XMLRPC_VALUE;
+/******/
+
+/****s* VALUE/XMLRPC_REQUEST
+ * NAME
+ *   XMLRPC_REQUEST
+ * NOTES
+ *   Internal representation of an XML request.
+ *
+ *  This struct is opaque to callers and should be accessed only via accessor functions.
+ *  
+ * SEE ALSO
+ *   XMLRPC_VALUE
+ *   XMLRPC_RequestNew ()
+ *   XMLRPC_RequestFree ()
+ * SOURCE
+ */
+typedef struct _xmlrpc_request {
+   XMLRPC_VALUE                         io;           /* data associated with this request */
+   simplestring                         methodName;   /* name of method being called       */
+   XMLRPC_REQUEST_TYPE                  request_type; /* type of request                   */
+   STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS output;       /* xml output options                */
+   XMLRPC_VALUE                         error;        /* error codes                       */
+} STRUCT_XMLRPC_REQUEST;
+/******/
+
+/* Vector type. Used by XMLRPC_VALUE.  Never visible to users of the API. */
+typedef struct _xmlrpc_vector {
+   XMLRPC_VECTOR_TYPE type;                           /* vector type                       */
+   queue *q;                                          /* list of child values              */
+} STRUCT_XMLRPC_VECTOR;
+/******/
+
+/****s* VALUE/XMLRPC_SERVER
+ * NAME
+ *   XMLRPC_SERVER
+ * NOTES
+ *   internal representation of an xmlrpc server
+ *
+ *  This struct is opaque to callers and should be accessed only via accessor functions.
+ *  
+ * SEE ALSO
+ *   XMLRPC_ServerCreate ()
+ *   XMLRPC_ServerDestroy ()
+ * SOURCE
+ */
+typedef struct _xmlrpc_server {
+   queue methodlist;                                  /* list of callback methods          */
+   queue docslist;                                    /* list of introspection callbacks   */
+   XMLRPC_VALUE xIntrospection;
+} STRUCT_XMLRPC_SERVER;
+/******/
+
+typedef struct _server_method {
+   char*                   name;
+   XMLRPC_VALUE            desc;
+   XMLRPC_Callback         method;
+} server_method;
+
+
+/*----------------------------------------------------------------------------
+ * Globals
+ */
+
+/*----------------------------------------------------------------------------
+ * Functions
+ */
+server_method* find_method(XMLRPC_SERVER server, const char* name);
+const char* type_to_str(XMLRPC_VALUE_TYPE type, XMLRPC_VECTOR_TYPE vtype);
+/*----------------------------------------------------------------------------
+ * Macros
+ */
+#define my_free(thing)  if(thing) {free(thing); thing = 0;}
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* XMLRPC_PRIVATE_ALREADY_INCLUDED */
+
diff --git a/php/xmlrpc/libxmlrpc/xmlrpc_win32.h b/php/xmlrpc/libxmlrpc/xmlrpc_win32.h
new file mode 100644 (file)
index 0000000..58c54bb
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef _XMLRPC_WIN32_H
+#define _XMLRPC_WIN32_H
+/* just some things needed to compile win32 */
+#include <windows.h>
+#include <stdlib.h>
+#define inline __inline
+#define snprintf _snprintf
+#define strcasecmp(s1, s2) stricmp(s1, s2)
+
+
+#endif
\ No newline at end of file
diff --git a/php/xmlrpc/php_xmlrpc.h b/php/xmlrpc/php_xmlrpc.h
new file mode 100644 (file)
index 0000000..24025d2
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+  This file is part of, or distributed with, libXMLRPC - a C library for 
+  xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+/* auto-generated portions of this file are also subject to the php license */
+
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2004 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.0 of the PHP license,       |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_0.txt.                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Author: Dan Libby                                                    |
+   +----------------------------------------------------------------------+
+ */
+
+/* $Id: php_xmlrpc.h 5574 2007-10-25 20:33:17Z thierry $ */
+
+#ifndef _PHP_XMLRPC_H
+#define _PHP_XMLRPC_H
+
+/* You should tweak config.m4 so this symbol (or some else suitable)
+   gets defined.
+*/
+#if 1 /* HAVE_XMLRPC */
+
+extern zend_module_entry xmlrpc_module_entry;
+#define phpext_xmlrpc_ptr &xmlrpc_module_entry
+
+#ifdef PHP_WIN32
+#define PHP_XMLRPC_API __declspec(dllexport)
+#else
+#define PHP_XMLRPC_API
+#endif
+
+PHP_MINIT_FUNCTION(xmlrpc);
+PHP_MSHUTDOWN_FUNCTION(xmlrpc);
+PHP_RINIT_FUNCTION(xmlrpc);
+PHP_RSHUTDOWN_FUNCTION(xmlrpc);
+PHP_MINFO_FUNCTION(xmlrpc);
+
+PHP_FUNCTION(xmlrpc_encode);
+PHP_FUNCTION(xmlrpc_decode);
+PHP_FUNCTION(xmlrpc_decode_request);
+PHP_FUNCTION(xmlrpc_encode_request);
+PHP_FUNCTION(xmlrpc_get_type);
+PHP_FUNCTION(xmlrpc_set_type);
+PHP_FUNCTION(xmlrpc_is_fault);
+PHP_FUNCTION(xmlrpc_server_create);
+PHP_FUNCTION(xmlrpc_server_destroy);
+PHP_FUNCTION(xmlrpc_server_register_method);
+PHP_FUNCTION(xmlrpc_server_call_method);
+PHP_FUNCTION(xmlrpc_parse_method_descriptions);
+PHP_FUNCTION(xmlrpc_server_add_introspection_data);
+PHP_FUNCTION(xmlrpc_server_register_introspection_callback);
+
+/* Fill in this structure and use entries in it
+   for thread safety instead of using true globals.
+*/
+ZEND_BEGIN_MODULE_GLOBALS(xmlrpc)
+       long allow_null;
+ZEND_END_MODULE_GLOBALS(xmlrpc)
+
+/* In every function that needs to use variables in zend_xmlrpc_globals,
+   do call XMLRPCLS_FETCH(); after declaring other variables used by
+   that function, and always refer to them as XMLRPCG(variable).
+   You are encouraged to rename these macros something shorter, see
+   examples in any other php module directory.
+*/
+
+#ifdef ZTS
+#define XMLRPCG(v) TSRMG(xmlrpc_globals_id, zend_xmlrpc_globals *, v)
+#define XMLRPCLS_FETCH() zend_xmlrpc_globals *xmlrpc_globals = ts_resource(xmlrpc_globals_id)
+#else
+#define XMLRPCG(v) (xmlrpc_globals.v)
+#define XMLRPCLS_FETCH()
+#endif
+
+ZEND_EXTERN_MODULE_GLOBALS(xmlrpc)
+
+#else
+
+#define phpext_xmlrpc_ptr NULL
+
+#endif
+
+#endif /* _PHP_XMLRPC_H */
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/php/xmlrpc/xmlrpc-epi-php.c b/php/xmlrpc/xmlrpc-epi-php.c
new file mode 100644 (file)
index 0000000..eb86a98
--- /dev/null
@@ -0,0 +1,1528 @@
+/*
+  This file is part of, or distributed with, libXMLRPC - a C library for 
+  xml-encoded function calls.
+
+  Author: Dan Libby (dan@libby.com)
+  Epinions.com may be contacted at feedback@epinions-inc.com
+*/
+
+/*  
+  Copyright 2001 Epinions, Inc. 
+
+  Subject to the following 3 conditions, Epinions, Inc.  permits you, free 
+  of charge, to (a) use, copy, distribute, modify, perform and display this 
+  software and associated documentation files (the "Software"), and (b) 
+  permit others to whom the Software is furnished to do so as well.  
+
+  1) The above copyright notice and this permission notice shall be included 
+  without modification in all copies or substantial portions of the 
+  Software.  
+
+  2) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OR CONDITION OF 
+  ANY KIND, EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY 
+  IMPLIED WARRANTIES OF ACCURACY, MERCHANTABILITY, FITNESS FOR A PARTICULAR 
+  PURPOSE OR NONINFRINGEMENT.  
+
+  3) IN NO EVENT SHALL EPINIONS, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, 
+  SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT 
+  OF OR IN CONNECTION WITH THE SOFTWARE (HOWEVER ARISING, INCLUDING 
+  NEGLIGENCE), EVEN IF EPINIONS, INC.  IS AWARE OF THE POSSIBILITY OF SUCH 
+  DAMAGES.    
+
+*/
+
+/* auto-generated portions of this file are also subject to the php license */
+
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2004 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.0 of the PHP license,       |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_0.txt.                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Author: Dan Libby                                                    |
+   +----------------------------------------------------------------------+
+ */
+
+/* $Id: xmlrpc-epi-php.c 5574 2007-10-25 20:33:17Z thierry $ */
+
+/**********************************************************************
+* BUGS:                                                               *
+*  - when calling a php user function, there appears to be no way to  *
+*    distinguish between a return value of null, and no return value  *
+*    at all.  The xml serialization layer(s) will then return a value *
+*    of null, when the right thing may be no value at all. (SOAP)     *
+**********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#include "ext/standard/info.h"
+#include "php_ini.h"
+#include "php_xmlrpc.h"
+#include "xmlrpc.h"
+
+#define PHP_EXT_VERSION "0.51"
+
+/* You should tweak config.m4 so this symbol (or some else suitable)
+       gets defined.  */
+
+ZEND_DECLARE_MODULE_GLOBALS(xmlrpc)
+
+static int le_xmlrpc_server;
+
+function_entry xmlrpc_functions[] = {
+       PHP_FE(xmlrpc_encode,                                                                   NULL) 
+       PHP_FE(xmlrpc_decode,                                                                   NULL)
+       PHP_FE(xmlrpc_decode_request,                                                   second_arg_force_ref)
+       PHP_FE(xmlrpc_encode_request,                                                   NULL)
+       PHP_FE(xmlrpc_get_type,                                                                 NULL)
+       PHP_FE(xmlrpc_set_type,                                                                 first_arg_force_ref)
+       PHP_FE(xmlrpc_is_fault,                                                                 NULL)
+       PHP_FE(xmlrpc_server_create,                                                    NULL)
+       PHP_FE(xmlrpc_server_destroy,                                                   NULL)
+       PHP_FE(xmlrpc_server_register_method,                                   NULL)
+       PHP_FE(xmlrpc_server_call_method,                                               NULL)
+       PHP_FE(xmlrpc_parse_method_descriptions,                                NULL)
+       PHP_FE(xmlrpc_server_add_introspection_data,                    NULL)
+       PHP_FE(xmlrpc_server_register_introspection_callback,   NULL)
+       {NULL, NULL, NULL}
+};
+
+zend_module_entry xmlrpc_module_entry = {
+       STANDARD_MODULE_HEADER,
+       "xmlrpc",
+       xmlrpc_functions,
+       PHP_MINIT(xmlrpc),
+       PHP_MSHUTDOWN(xmlrpc),
+       PHP_RINIT(xmlrpc),      /* Replace with NULL if there's nothing to do at request start */
+       PHP_RSHUTDOWN(xmlrpc),  /* Replace with NULL if there's nothing to do at request end */
+       PHP_MINFO(xmlrpc),
+       PHP_EXT_VERSION,
+       STANDARD_MODULE_PROPERTIES
+};
+
+#ifdef COMPILE_DL_XMLRPC
+ZEND_GET_MODULE(xmlrpc)
+# ifdef PHP_WIN32
+# include "zend_arg_defs.c"
+# endif
+#endif
+
+PHP_INI_BEGIN()
+STD_PHP_INI_BOOLEAN("xmlrpc.allow_null", "0", PHP_INI_ALL, OnUpdateBool, allow_null, zend_xmlrpc_globals, xmlrpc_globals)
+PHP_INI_END()
+
+static void php_xmlrpc_init_globals(zend_xmlrpc_globals *xmlrpc_globals)
+{
+       memset(xmlrpc_globals, 0, sizeof(zend_xmlrpc_globals));
+}
+
+/*******************************
+* local structures and defines *
+*******************************/
+
+/* per server data */
+typedef struct _xmlrpc_server_data {
+       zval* method_map;
+       zval* introspection_map;
+       XMLRPC_SERVER server_ptr;
+} xmlrpc_server_data;
+
+
+/* how to format output */
+typedef struct _php_output_options {
+       int b_php_out;
+       int b_auto_version;
+       int b_allow_null;
+       STRUCT_XMLRPC_REQUEST_OUTPUT_OPTIONS xmlrpc_out;
+} php_output_options;
+
+/* data passed to C callback */
+typedef struct _xmlrpc_callback_data {
+       zval* xmlrpc_method;
+       zval* php_function;
+       zval* caller_params;
+       zval* return_data;
+       xmlrpc_server_data* server;
+       char php_executed;
+} xmlrpc_callback_data;
+
+/* output options */
+#define OUTPUT_TYPE_KEY       "output_type"
+#define OUTPUT_TYPE_KEY_LEN   (sizeof(OUTPUT_TYPE_KEY) - 1)
+#define OUTPUT_TYPE_VALUE_PHP "php"
+#define OUTPUT_TYPE_VALUE_XML "xml"
+
+#define ALLOW_NULL_KEY     "allow_null"
+#define ALLOW_NULL_KEY_LEN (sizeof(ALLOW_NULL_KEY) - 1)
+
+#define VERBOSITY_KEY                  "verbosity"
+#define VERBOSITY_KEY_LEN              (sizeof(VERBOSITY_KEY) - 1)
+#define VERBOSITY_VALUE_NO_WHITE_SPACE "no_white_space"
+#define VERBOSITY_VALUE_NEWLINES_ONLY  "newlines_only"
+#define VERBOSITY_VALUE_PRETTY         "pretty"
+
+#define ESCAPING_KEY             "escaping"
+#define ESCAPING_KEY_LEN         (sizeof(ESCAPING_KEY) - 1)
+#define ESCAPING_VALUE_CDATA     "cdata"
+#define ESCAPING_VALUE_NON_ASCII "non-ascii"
+#define ESCAPING_VALUE_NON_PRINT "non-print"
+#define ESCAPING_VALUE_MARKUP    "markup"
+
+#define VERSION_KEY          "version"
+#define VERSION_KEY_LEN      (sizeof(VERSION_KEY) - 1)
+#define VERSION_VALUE_SIMPLE "simple"
+#define VERSION_VALUE_XMLRPC "xmlrpc"
+#define VERSION_VALUE_SOAP11 "soap 1.1"
+#define VERSION_VALUE_AUTO   "auto"
+
+#define ENCODING_KEY     "encoding"
+#define ENCODING_KEY_LEN (sizeof(ENCODING_KEY) - 1)
+#define ENCODING_DEFAULT "iso-8859-1"
+
+/* value types */
+#define OBJECT_TYPE_ATTR  "xmlrpc_type"
+#define OBJECT_VALUE_ATTR "scalar"
+#define OBJECT_VALUE_TS_ATTR "timestamp"
+
+/* faults */
+#define FAULT_CODE       "faultCode"
+#define FAULT_CODE_LEN   (sizeof(FAULT_CODE) - 1)
+#define FAULT_STRING     "faultString"
+#define FAULT_STRING_LEN (sizeof(FAULT_STRING) - 1)
+
+/***********************
+* forward declarations *
+***********************/
+XMLRPC_VALUE_TYPE get_zval_xmlrpc_type(php_output_options* out, zval* value, zval** newvalue);
+static void php_xmlrpc_introspection_callback(XMLRPC_SERVER server, void* data);
+int sset_zval_xmlrpc_type(zval* value, XMLRPC_VALUE_TYPE type);
+zval* decode_request_worker(zval* xml_in, zval* encoding_in, zval* method_name_out);
+const char* xmlrpc_type_as_str(XMLRPC_VALUE_TYPE type, XMLRPC_VECTOR_TYPE vtype);
+XMLRPC_VALUE_TYPE xmlrpc_str_as_type(const char* str);
+XMLRPC_VECTOR_TYPE xmlrpc_str_as_vector_type(const char* str);
+int set_zval_xmlrpc_type(zval* value, XMLRPC_VALUE_TYPE type);
+
+/*********************
+* startup / shutdown *
+*********************/
+
+static void destroy_server_data(xmlrpc_server_data *server)
+{
+       if (server) {
+               XMLRPC_ServerDestroy(server->server_ptr);
+
+               zval_dtor(server->method_map);
+               FREE_ZVAL(server->method_map);
+
+               zval_dtor(server->introspection_map);
+               FREE_ZVAL(server->introspection_map);
+
+               efree(server);
+       }
+}
+
+/* called when server is being destructed. either when xmlrpc_server_destroy
+ * is called, or when request ends.  */
+static void xmlrpc_server_destructor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+       if (rsrc && rsrc->ptr) {
+               destroy_server_data((xmlrpc_server_data*) rsrc->ptr);
+       }
+}
+
+/* module init */
+PHP_MINIT_FUNCTION(xmlrpc)
+{
+       ZEND_INIT_MODULE_GLOBALS(xmlrpc, php_xmlrpc_init_globals, NULL);
+
+       REGISTER_INI_ENTRIES();
+
+       le_xmlrpc_server = zend_register_list_destructors_ex(xmlrpc_server_destructor, NULL, "xmlrpc server", module_number);
+
+       return SUCCESS;
+}
+
+/* module shutdown */
+PHP_MSHUTDOWN_FUNCTION(xmlrpc)
+{
+       return SUCCESS;
+}
+
+/* Remove if there's nothing to do at request start */
+PHP_RINIT_FUNCTION(xmlrpc)
+{
+       return SUCCESS;
+}
+
+/* Remove if there's nothing to do at request end */
+PHP_RSHUTDOWN_FUNCTION(xmlrpc)
+{
+       return SUCCESS;
+}
+
+/* display info in phpinfo() */
+PHP_MINFO_FUNCTION(xmlrpc)
+{
+       php_info_print_table_start();
+       php_info_print_table_row(2, "core library version", XMLRPC_GetVersionString());
+       php_info_print_table_row(2, "php extension version", PHP_EXT_VERSION);
+       php_info_print_table_row(2, "author", "Dan Libby");
+       php_info_print_table_row(2, "homepage", "http://xmlrpc-epi.sourceforge.net");
+       php_info_print_table_row(2, "open sourced by", "Epinions.com");
+       php_info_print_table_end();
+}
+
+/*******************
+* random utilities *
+*******************/
+
+/* Utility functions for adding data types to arrays, with or without key (assoc, non-assoc).
+ * Could easily be further generalized to work with objects.
+ */
+#if 0
+static int add_long(zval* list, char* id, int num) {
+       if(id) return add_assoc_long(list, id, num);
+       else   return add_next_index_long(list, num);
+}
+
+static int add_double(zval* list, char* id, double num) {
+       if(id) return add_assoc_double(list, id, num);
+       else   return add_next_index_double(list, num);
+}
+
+static int add_string(zval* list, char* id, char* string, int duplicate) {
+       if(id) return add_assoc_string(list, id, string, duplicate);
+       else   return add_next_index_string(list, string, duplicate);
+}
+
+static int add_stringl(zval* list, char* id, char* string, uint length, int duplicate) {
+       if(id) return add_assoc_stringl(list, id, string, length, duplicate);
+       else   return add_next_index_stringl(list, string, length, duplicate);
+}
+
+#endif
+
+static int add_zval(zval* list, const char* id, zval** val)
+{
+       if (list && val) {
+               if (id) {
+                       return zend_hash_update(Z_ARRVAL_P(list), (char*) id, strlen(id) + 1, (void *) val, sizeof(zval **), NULL);
+               } else {
+                       return zend_hash_next_index_insert(Z_ARRVAL_P(list), (void *) val, sizeof(zval **), NULL); 
+               }
+       }
+       /* what is the correct return on error? */
+       return 0;
+}
+
+#define my_zend_hash_get_current_key(ht, my_key, num_index) zend_hash_get_current_key(ht, my_key, num_index, 0)
+
+
+/*************************
+* input / output options *
+*************************/
+
+/* parse an array (user input) into output options suitable for use by xmlrpc engine
+ * and determine whether to return data as xml or php vars */
+static void set_output_options(php_output_options* options, zval* output_opts)
+{
+       XMLRPCLS_FETCH();
+
+       if (options) {
+
+               /* defaults */
+               options->b_php_out = 0;
+               options->b_auto_version = 1;
+               options->b_allow_null = 0;
+               options->xmlrpc_out.version = xmlrpc_version_1_0;
+               options->xmlrpc_out.xml_elem_opts.encoding = ENCODING_DEFAULT;
+               options->xmlrpc_out.xml_elem_opts.verbosity = xml_elem_pretty;
+               options->xmlrpc_out.xml_elem_opts.escaping = xml_elem_markup_escaping | xml_elem_non_ascii_escaping | xml_elem_non_print_escaping;
+
+               if (output_opts && Z_TYPE_P(output_opts) == IS_ARRAY) {
+                       zval** val;
+
+                       /* marshal NULL */
+                       if (zend_hash_find(Z_ARRVAL_P(output_opts), ALLOW_NULL_KEY, ALLOW_NULL_KEY_LEN + 1, (void**) &val) == SUCCESS) {
+                               if (Z_TYPE_PP(val) == IS_BOOL) {
+                                       if (Z_LVAL_PP(val)) {
+                                               options->b_allow_null = 1;
+                                       } else {
+                                               options->b_allow_null = 0;
+                                       }
+                               }
+                       }
+
+                       /* type of output (xml/php) */
+                       if (zend_hash_find(Z_ARRVAL_P(output_opts), OUTPUT_TYPE_KEY, OUTPUT_TYPE_KEY_LEN + 1, (void**) &val) == SUCCESS) {
+                               if (Z_TYPE_PP(val) == IS_STRING) {
+                                       if (!strcmp(Z_STRVAL_PP(val), OUTPUT_TYPE_VALUE_PHP)) {
+                                               options->b_php_out = 1;
+                                       } else if (!strcmp(Z_STRVAL_PP(val), OUTPUT_TYPE_VALUE_XML)) {
+                                               options->b_php_out = 0;
+                                       }
+                               }
+                       }
+
+                       /* verbosity of generated xml */
+                       if (zend_hash_find(Z_ARRVAL_P(output_opts), VERBOSITY_KEY, VERBOSITY_KEY_LEN + 1, (void**) &val) == SUCCESS) {
+                               if (Z_TYPE_PP(val) == IS_STRING) {
+                                       if (!strcmp(Z_STRVAL_PP(val), VERBOSITY_VALUE_NO_WHITE_SPACE)) {
+                                               options->xmlrpc_out.xml_elem_opts.verbosity = xml_elem_no_white_space;
+                                       } else if (!strcmp(Z_STRVAL_PP(val), VERBOSITY_VALUE_NEWLINES_ONLY)) {
+                                               options->xmlrpc_out.xml_elem_opts.verbosity = xml_elem_newlines_only;
+                                       } else if (!strcmp(Z_STRVAL_PP(val), VERBOSITY_VALUE_PRETTY)) {
+                                               options->xmlrpc_out.xml_elem_opts.verbosity = xml_elem_pretty;
+                                       }
+                               }
+                       }
+
+                       /* version of xml to output */
+                       if (zend_hash_find(Z_ARRVAL_P(output_opts), VERSION_KEY, VERSION_KEY_LEN + 1, (void**) &val) == SUCCESS) {
+                               if (Z_TYPE_PP(val) == IS_STRING) {
+                                       options->b_auto_version = 0;
+                                       if (!strcmp(Z_STRVAL_PP(val), VERSION_VALUE_XMLRPC)) {
+                                               options->xmlrpc_out.version = xmlrpc_version_1_0;
+                                       } else if (!strcmp(Z_STRVAL_PP(val), VERSION_VALUE_SIMPLE)) {
+                                               options->xmlrpc_out.version = xmlrpc_version_simple;
+                                       } else if (!strcmp((*val)->value.str.val, VERSION_VALUE_SOAP11)) {
+                                                       options->xmlrpc_out.version = xmlrpc_version_soap_1_1;
+                                       } else { /* if(!strcmp((*val)->value.str.val, VERSION_VALUE_AUTO)) { */
+                                                       options->b_auto_version = 1;
+                                       }
+                               }
+                       }
+
+                 /* encoding code set */
+                 if(zend_hash_find(Z_ARRVAL_P(output_opts), 
+                                   ENCODING_KEY, ENCODING_KEY_LEN + 1, 
+                                   (void**)&val) == SUCCESS) {
+                    if(Z_TYPE_PP(val) == IS_STRING) {
+                       options->xmlrpc_out.xml_elem_opts.encoding = estrdup(Z_STRVAL_PP(val));
+                    }
+                 }
+
+                 /* escaping options */
+                 if(zend_hash_find(Z_ARRVAL_P(output_opts), 
+                                   ESCAPING_KEY, ESCAPING_KEY_LEN + 1, 
+                                   (void**)&val) == SUCCESS) {
+                    /* multiple values allowed.  check if array */
+                    if(Z_TYPE_PP(val) == IS_ARRAY) {
+                       zval** iter_val;
+                       zend_hash_internal_pointer_reset(Z_ARRVAL_PP(val));
+                       options->xmlrpc_out.xml_elem_opts.escaping = xml_elem_no_escaping;
+                       while(1) {
+                          if(zend_hash_get_current_data(Z_ARRVAL_PP(val), (void**)&iter_val) == SUCCESS) {
+                             if(Z_TYPE_PP(iter_val) == IS_STRING && Z_STRVAL_PP(iter_val)) {
+                                if(!strcmp(Z_STRVAL_PP(iter_val), ESCAPING_VALUE_CDATA)) {
+                                   options->xmlrpc_out.xml_elem_opts.escaping |= xml_elem_cdata_escaping;
+                                }
+                                else if(!strcmp(Z_STRVAL_PP(iter_val), ESCAPING_VALUE_NON_ASCII)) {
+                                   options->xmlrpc_out.xml_elem_opts.escaping |= xml_elem_non_ascii_escaping;
+                                }
+                                else if(!strcmp(Z_STRVAL_PP(iter_val), ESCAPING_VALUE_NON_PRINT)) {
+                                   options->xmlrpc_out.xml_elem_opts.escaping |= xml_elem_non_print_escaping;
+                                }
+                                else if(!strcmp(Z_STRVAL_PP(iter_val), ESCAPING_VALUE_MARKUP)) {
+                                   options->xmlrpc_out.xml_elem_opts.escaping |= xml_elem_markup_escaping;
+                                }
+                             }
+                          }
+                          else {
+                             break;
+                          }
+
+                          zend_hash_move_forward(Z_ARRVAL_PP(val));
+                       }
+                    }
+                    /* else, check for single value */
+                    else if(Z_TYPE_PP(val) == IS_STRING) {
+                       if(!strcmp(Z_STRVAL_PP(val), ESCAPING_VALUE_CDATA)) {
+                          options->xmlrpc_out.xml_elem_opts.escaping = xml_elem_cdata_escaping;
+                       }
+                       else if(!strcmp(Z_STRVAL_PP(val), ESCAPING_VALUE_NON_ASCII)) {
+                          options->xmlrpc_out.xml_elem_opts.escaping = xml_elem_non_ascii_escaping;
+                       }
+                       else if(!strcmp(Z_STRVAL_PP(val), ESCAPING_VALUE_NON_PRINT)) {
+                          options->xmlrpc_out.xml_elem_opts.escaping = xml_elem_non_print_escaping;
+                       }
+                       else if(!strcmp(Z_STRVAL_PP(val), ESCAPING_VALUE_MARKUP)) {
+                          options->xmlrpc_out.xml_elem_opts.escaping = xml_elem_markup_escaping;
+                       }
+                    }
+                 }
+         }
+       }
+}
+
+
+/******************
+* encode / decode *
+******************/
+
+/* php arrays have no distinction between array and struct types.
+ * they even allow mixed.  Thus, we determine the type by iterating
+ * through the entire array and figuring out each element.
+ * room for some optimation here if we stop after a specific # of elements.
+ */
+static XMLRPC_VECTOR_TYPE determine_vector_type (HashTable *ht)
+{
+    int bArray = 0, bStruct = 0, bMixed = 0;
+    unsigned long num_index;
+    char* my_key;
+
+    zend_hash_internal_pointer_reset(ht);
+    while(1) {
+       int res = my_zend_hash_get_current_key(ht, &my_key, &num_index);
+       if(res == HASH_KEY_IS_LONG) {
+           if(bStruct) {
+               bMixed = 1;
+               break;
+           }
+           bArray = 1;
+       }
+       else if(res == HASH_KEY_NON_EXISTANT) {
+          break;
+       }
+       else if(res == HASH_KEY_IS_STRING) {
+           if(bArray) {
+               bMixed = 1;
+               break;
+           }
+           bStruct = 1;
+       }
+
+       zend_hash_move_forward(ht);
+    }
+    return bMixed ? xmlrpc_vector_mixed : (bStruct ? xmlrpc_vector_struct : xmlrpc_vector_array);
+}
+
+/* recursively convert php values into xmlrpc values */
+static XMLRPC_VALUE PHP_to_XMLRPC_worker (php_output_options* out, const char* key, zval* in_val, int depth)
+{
+   XMLRPC_VALUE xReturn = NULL;
+   if(in_val) {
+      zval* val = NULL;
+      XMLRPC_VALUE_TYPE type = get_zval_xmlrpc_type(out, in_val, &val);
+      if(val) {
+         switch(type) {
+            case xmlrpc_nil:
+               xReturn = XMLRPC_CreateValueEmpty();
+               XMLRPC_SetValueID(xReturn, key, 0);
+               break;
+            case xmlrpc_base64:
+               xReturn = XMLRPC_CreateValueBase64(key, Z_STRVAL_P(val), Z_STRLEN_P(val));
+               break;
+            case xmlrpc_datetime:
+               convert_to_string(val);
+               xReturn = XMLRPC_CreateValueDateTime_ISO8601(key, Z_STRVAL_P(val));
+               break;
+            case xmlrpc_boolean:
+               convert_to_boolean(val);
+               xReturn = XMLRPC_CreateValueBoolean(key, Z_LVAL_P(val));
+               break;
+            case xmlrpc_int:
+               convert_to_long(val);
+               xReturn = XMLRPC_CreateValueInt(key, Z_LVAL_P(val));
+               break;
+            case xmlrpc_double:
+               convert_to_double(val);
+               xReturn = XMLRPC_CreateValueDouble(key, Z_DVAL_P(val));
+               break;
+            case xmlrpc_string:
+               convert_to_string(val);
+               xReturn = XMLRPC_CreateValueString(key, Z_STRVAL_P(val), Z_STRLEN_P(val));
+               break;
+            case xmlrpc_vector:
+               {
+                  unsigned long num_index;
+                  zval** pIter;
+                  char* my_key;
+
+                  convert_to_array(val);
+
+                  xReturn = XMLRPC_CreateVector(key, determine_vector_type(Z_ARRVAL_P(val)));
+
+                  zend_hash_internal_pointer_reset(Z_ARRVAL_P(val));
+                  while(1) {
+                     int res = my_zend_hash_get_current_key(Z_ARRVAL_P(val), &my_key, &num_index);
+                     if(res == HASH_KEY_IS_LONG) {
+                        if(zend_hash_get_current_data(Z_ARRVAL_P(val), (void**)&pIter) == SUCCESS) {
+                           XMLRPC_AddValueToVector(xReturn, PHP_to_XMLRPC_worker(out, 0, *pIter, depth++));
+                        }
+                     }
+                     else if(res == HASH_KEY_NON_EXISTANT) {
+                        break;
+                     }
+                     else if(res == HASH_KEY_IS_STRING) {
+                        if(zend_hash_get_current_data(Z_ARRVAL_P(val), (void**)&pIter) == SUCCESS) {
+                           XMLRPC_AddValueToVector(xReturn, PHP_to_XMLRPC_worker(out, my_key, *pIter, depth++));
+                        }
+                     }
+
+                     zend_hash_move_forward(Z_ARRVAL_P(val));
+                  }
+               }
+               break;
+            default:
+               break;
+         }
+      }
+   }
+   return xReturn;
+}
+
+static XMLRPC_VALUE PHP_to_XMLRPC(php_output_options* out, zval* root_val)
+{
+   return PHP_to_XMLRPC_worker(out, NULL, root_val, 0);
+}
+
+/* recursively convert xmlrpc values into php values */
+static zval* XMLRPC_to_PHP(XMLRPC_VALUE el)
+{
+   zval* elem = NULL;
+   const char* pStr;
+
+   if(el) {
+      XMLRPC_VALUE_TYPE type = XMLRPC_GetValueType(el);
+
+      MAKE_STD_ZVAL(elem); /* init. very important.  spent a frustrating day finding this out. */
+
+      switch(type) {
+         case xmlrpc_empty:
+         case xmlrpc_nil:
+            Z_TYPE_P(elem) = IS_NULL;
+            break;
+         case xmlrpc_string:
+            pStr = XMLRPC_GetValueString(el);
+            if(pStr) {
+               Z_STRLEN_P(elem) = XMLRPC_GetValueStringLen(el);
+               Z_STRVAL_P(elem) = estrndup(pStr, Z_STRLEN_P(elem));
+               Z_TYPE_P(elem) = IS_STRING;
+            }
+            break;
+         case xmlrpc_int:
+            Z_LVAL_P(elem) = XMLRPC_GetValueInt(el);
+            Z_TYPE_P(elem) = IS_LONG;
+            break;
+         case xmlrpc_boolean:
+            Z_LVAL_P(elem) = XMLRPC_GetValueBoolean(el);
+            Z_TYPE_P(elem) = IS_BOOL;
+            break;
+         case xmlrpc_double:
+            Z_DVAL_P(elem) = XMLRPC_GetValueDouble(el);
+            Z_TYPE_P(elem) = IS_DOUBLE;
+            break;
+         case xmlrpc_datetime:
+            Z_STRLEN_P(elem) = XMLRPC_GetValueStringLen(el);
+            Z_STRVAL_P(elem) = estrndup(XMLRPC_GetValueDateTime_ISO8601(el), Z_STRLEN_P(elem));
+            Z_TYPE_P(elem) = IS_STRING;
+            break;
+         case xmlrpc_base64:
+            pStr = XMLRPC_GetValueBase64(el);
+            if(pStr) {
+               Z_STRLEN_P(elem) = XMLRPC_GetValueStringLen(el);
+               Z_STRVAL_P(elem) = estrndup(pStr, Z_STRLEN_P(elem));
+               Z_TYPE_P(elem) = IS_STRING;
+            }
+            break;
+         case xmlrpc_vector:
+               array_init(elem);
+               {
+                       XMLRPC_VALUE xIter = XMLRPC_VectorRewind(el);
+
+                       while( xIter ) {
+                               zval *val = XMLRPC_to_PHP(xIter);
+                               if (val) {
+                                       add_zval(elem, XMLRPC_GetValueID(xIter), &val);
+                               }
+                               xIter = XMLRPC_VectorNext(el);
+                       }
+               }
+               break;
+         default:
+            break;
+      }
+      set_zval_xmlrpc_type(elem, type);
+   }
+   return elem;
+}
+
+/* {{{ proto string xmlrpc_encode_request(string method, mixed params)
+   Generates XML for a method request */
+PHP_FUNCTION(xmlrpc_encode_request)
+{
+       XMLRPC_REQUEST xRequest = NULL;
+       zval **method, **vals, **out_opts;
+       char* outBuf;
+       php_output_options out;
+
+       if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > 3 || (zend_get_parameters_ex(ZEND_NUM_ARGS(), &method, &vals, &out_opts) == FAILURE)) {
+               WRONG_PARAM_COUNT; /* prints/logs a warning and returns */
+       }
+
+       set_output_options(&out, (ZEND_NUM_ARGS() == 3) ? *out_opts : 0);
+
+       if(return_value_used) {
+               xRequest = XMLRPC_RequestNew();
+
+               if(xRequest) {
+                       XMLRPC_RequestSetOutputOptions(xRequest, &out.xmlrpc_out);
+                       if (Z_TYPE_PP(method) == IS_NULL) {
+                               XMLRPC_RequestSetRequestType(xRequest, xmlrpc_request_response);
+                       } else {
+                               XMLRPC_RequestSetMethodName(xRequest, Z_STRVAL_PP(method));
+                               XMLRPC_RequestSetRequestType(xRequest, xmlrpc_request_call);
+                       }
+                       if (Z_TYPE_PP(vals) != IS_NULL) {
+                               XMLRPC_RequestSetData(xRequest, PHP_to_XMLRPC(&out, *vals));
+                       }
+
+                       outBuf = XMLRPC_REQUEST_ToXML(xRequest, 0);
+                       if(outBuf) {
+                               RETVAL_STRING(outBuf, 1);
+                               free(outBuf);
+                       }
+                       XMLRPC_RequestFree(xRequest, 1);
+               }
+       }
+}
+/* }}} */
+
+/* {{{ proto string xmlrpc_encode(mixed value)
+   Generates XML for a PHP value */
+PHP_FUNCTION(xmlrpc_encode)
+{
+       XMLRPC_VALUE xOut = NULL;
+       zval **arg1;
+       char *outBuf;
+
+       if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &arg1) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       if( return_value_used ) {
+               /* convert native php type to xmlrpc type */
+               xOut = PHP_to_XMLRPC(NULL, *arg1);
+
+               /* generate raw xml from xmlrpc data */
+               outBuf = XMLRPC_VALUE_ToXML(xOut, 0);
+
+               if(xOut) {
+                       if(outBuf) {
+                               RETVAL_STRING(outBuf, 1);
+                               free(outBuf);
+                       }
+                       /* cleanup */
+                       XMLRPC_CleanupValue(xOut);
+               }
+       }
+}
+/* }}} */
+
+
+zval* decode_request_worker (zval* xml_in, zval* encoding_in, zval* method_name_out)
+{
+   zval* retval = NULL;
+   XMLRPC_REQUEST response;
+   STRUCT_XMLRPC_REQUEST_INPUT_OPTIONS opts = {{0}};
+   opts.xml_elem_opts.encoding = encoding_in ? utf8_get_encoding_id_from_string(Z_STRVAL_P(encoding_in)) : ENCODING_DEFAULT;
+
+   /* generate XMLRPC_REQUEST from raw xml */
+   response = XMLRPC_REQUEST_FromXML(Z_STRVAL_P(xml_in), Z_STRLEN_P(xml_in), &opts);
+   if(response) {
+      /* convert xmlrpc data to native php types */
+      retval = XMLRPC_to_PHP(XMLRPC_RequestGetData(response));
+
+      if(XMLRPC_RequestGetRequestType(response) == xmlrpc_request_call) {
+         if(method_name_out) {
+            convert_to_string(method_name_out);
+            Z_TYPE_P(method_name_out) = IS_STRING;
+            Z_STRVAL_P(method_name_out) = estrdup(XMLRPC_RequestGetMethodName(response));
+            Z_STRLEN_P(method_name_out) = strlen(Z_STRVAL_P(method_name_out));
+         }
+      }
+
+      /* dust, sweep, and mop */
+      XMLRPC_RequestFree(response, 1);
+   }
+   return retval;
+}
+
+/* {{{ proto array xmlrpc_decode_request(string xml, string& method [, string encoding])
+   Decodes XML into native PHP types */
+PHP_FUNCTION(xmlrpc_decode_request)
+{
+       zval **xml, **method, **encoding = NULL;
+       int argc = ZEND_NUM_ARGS();
+
+       if (argc < 2 || argc > 3 || (zend_get_parameters_ex(argc, &xml, &method, &encoding) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_string_ex(xml);
+       convert_to_string_ex(method);
+       if(argc == 3) {
+               convert_to_string_ex(encoding);
+       }
+
+       if(return_value_used) {
+               zval* retval = decode_request_worker(*xml, encoding ? *encoding : NULL, *method);
+               if(retval) {
+                       *return_value = *retval;
+                       FREE_ZVAL(retval);
+               }
+       }
+}
+/* }}} */
+
+
+/* {{{ proto array xmlrpc_decode(string xml [, string encoding])
+   Decodes XML into native PHP types */
+PHP_FUNCTION(xmlrpc_decode)
+{
+       zval **arg1, **arg2 = NULL;
+       int argc = ZEND_NUM_ARGS();
+
+       if (argc < 1 || argc > 2 || (zend_get_parameters_ex(argc, &arg1, &arg2) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_string_ex(arg1);
+       if(argc == 2) {
+               convert_to_string_ex(arg2);
+       }
+
+       if(return_value_used) {
+               zval* retval = decode_request_worker(*arg1, arg2 ? *arg2 : NULL, NULL);
+               if(retval) {
+                       *return_value = *retval;
+                       FREE_ZVAL(retval);
+               }
+       }
+}
+/* }}} */
+
+
+/*************************
+* server related methods *
+*************************/
+
+/* {{{ proto resource xmlrpc_server_create(void)
+   Creates an xmlrpc server */
+PHP_FUNCTION(xmlrpc_server_create)
+{
+       if(ZEND_NUM_ARGS() != 0) {
+               WRONG_PARAM_COUNT;
+       }
+
+       if(return_value_used) {
+               zval *method_map, *introspection_map;
+               xmlrpc_server_data *server = emalloc(sizeof(xmlrpc_server_data));
+               MAKE_STD_ZVAL(method_map);
+               MAKE_STD_ZVAL(introspection_map);
+               
+               array_init(method_map);
+               array_init(introspection_map);
+               
+               /* allocate server data.  free'd in destroy_server_data() */
+               server->method_map = method_map;
+               server->introspection_map = introspection_map;
+               server->server_ptr = XMLRPC_ServerCreate();
+
+               XMLRPC_ServerRegisterIntrospectionCallback(server->server_ptr, php_xmlrpc_introspection_callback);
+
+               /* store for later use */
+               ZEND_REGISTER_RESOURCE(return_value,server, le_xmlrpc_server);
+       }
+}
+/* }}} */
+
+/* {{{ proto int xmlrpc_server_destroy(resource server)
+   Destroys server resources */
+PHP_FUNCTION(xmlrpc_server_destroy)
+{
+       zval **arg1;
+       int bSuccess = FAILURE;
+
+       if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &arg1) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       if(Z_TYPE_PP(arg1) == IS_RESOURCE) {
+               int type;
+
+               xmlrpc_server_data *server = zend_list_find(Z_LVAL_PP(arg1), &type);
+
+               if(server && type == le_xmlrpc_server) {
+                       bSuccess = zend_list_delete(Z_LVAL_PP(arg1));
+
+                       /* called by hashtable destructor
+                        * destroy_server_data(server);
+                        */
+               }
+       }
+       RETVAL_LONG(bSuccess == SUCCESS);
+}
+/* }}} */
+
+           
+/* called by xmlrpc C engine as method handler for all registered methods.
+ * it then calls the corresponding PHP function to handle the method.
+ */
+static XMLRPC_VALUE php_xmlrpc_callback(XMLRPC_SERVER server, XMLRPC_REQUEST xRequest, void* data)
+{
+   xmlrpc_callback_data* pData = (xmlrpc_callback_data*)data;
+   zval* xmlrpc_params;
+   zval* callback_params[3];
+   TSRMLS_FETCH();
+   
+   /* convert xmlrpc to native php types */
+   xmlrpc_params = XMLRPC_to_PHP(XMLRPC_RequestGetData(xRequest));
+
+   /* setup data hoojum */
+   callback_params[0] = pData->xmlrpc_method;
+   callback_params[1] = xmlrpc_params;
+   callback_params[2] = pData->caller_params;
+
+   /* Use same C function for all methods */
+
+   /* php func prototype: function user_func($method_name, $xmlrpc_params, $user_params) */
+   call_user_function(CG(function_table), NULL, pData->php_function, pData->return_data, 3, callback_params TSRMLS_CC);
+
+   pData->php_executed = 1;
+
+       zval_dtor(xmlrpc_params);
+       FREE_ZVAL(xmlrpc_params);
+
+       return NULL;
+}
+
+/* called by the C server when it first receives an introspection request.  We pass this on to
+ * our PHP listeners, if any
+ */
+static void php_xmlrpc_introspection_callback(XMLRPC_SERVER server, void* data)
+{
+   zval *retval_ptr, **php_function;
+   zval* callback_params[1];
+   xmlrpc_callback_data* pData = (xmlrpc_callback_data*)data;
+   TSRMLS_FETCH();
+   
+   MAKE_STD_ZVAL(retval_ptr);
+   Z_TYPE_P(retval_ptr) = IS_NULL;
+
+   /* setup data hoojum */
+   callback_params[0] = pData->caller_params;
+
+   /* loop through and call all registered callbacks */
+   zend_hash_internal_pointer_reset(Z_ARRVAL_P(pData->server->introspection_map));
+   while(1) {
+      if(zend_hash_get_current_data(Z_ARRVAL_P(pData->server->introspection_map), 
+                                    (void**)&php_function) == SUCCESS) {
+
+         /* php func prototype: function string user_func($user_params) */
+         if(call_user_function(CG(function_table), NULL, *php_function, 
+                               retval_ptr, 1, callback_params TSRMLS_CC) == SUCCESS) {
+            XMLRPC_VALUE xData;
+            STRUCT_XMLRPC_ERROR err = {0};
+
+            /* return value should be a string */
+            convert_to_string(retval_ptr);
+
+            xData = XMLRPC_IntrospectionCreateDescription(Z_STRVAL_P(retval_ptr), &err);
+
+            if(xData) {
+               if(!XMLRPC_ServerAddIntrospectionData(server, xData)) {
+                  php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to add introspection data returned from %s(), improper element structure", Z_STRVAL_PP(php_function));
+               }
+               XMLRPC_CleanupValue(xData);
+            }
+            else {
+               /* could not create description */
+               if(err.xml_elem_error.parser_code) {
+                  php_error_docref(NULL TSRMLS_CC, E_WARNING, "xml parse error: [line %ld, column %ld, message: %s] Unable to add introspection data returned from %s()", 
+                             err.xml_elem_error.column, err.xml_elem_error.line, err.xml_elem_error.parser_error, Z_STRVAL_PP(php_function));
+               }
+               else {
+                  php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to add introspection data returned from %s()", 
+                             Z_STRVAL_PP(php_function));
+               }
+            }
+         }
+         else {
+            /* user func failed */
+            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error calling user introspection callback: %s()", Z_STRVAL_PP(php_function));
+         }
+      }
+      else {
+         break;
+      }
+
+      zend_hash_move_forward(Z_ARRVAL_P(pData->server->introspection_map));
+   }
+
+   /* so we don't call the same callbacks ever again */
+   zend_hash_clean(Z_ARRVAL_P(pData->server->introspection_map));
+}
+
+/* {{{ proto bool xmlrpc_server_register_method(resource server, string method_name, string function)
+   Register a PHP function to handle method matching method_name */
+PHP_FUNCTION(xmlrpc_server_register_method)
+{
+       zval **method_key, **method_name, **handle, *method_name_save;
+       int type;
+       xmlrpc_server_data* server;
+
+       if (ZEND_NUM_ARGS() != 3 || (zend_get_parameters_ex(3, &handle, &method_key, &method_name) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       server = zend_list_find(Z_LVAL_PP(handle), &type);
+
+       if(type == le_xmlrpc_server) {
+               /* register with C engine. every method just calls our standard callback, 
+                * and it then dispatches to php as necessary
+                */
+               if(XMLRPC_ServerRegisterMethod(server->server_ptr, Z_STRVAL_PP(method_key), php_xmlrpc_callback)) {
+                       /* save for later use */
+                       MAKE_STD_ZVAL(method_name_save);
+                       *method_name_save = **method_name;
+                       zval_copy_ctor(method_name_save);
+
+                       /* register our php method */
+                       add_zval(server->method_map, Z_STRVAL_PP(method_key), &method_name_save);
+
+                       RETURN_BOOL(1);
+               }
+       }
+       RETURN_BOOL(0);
+}
+/* }}} */
+
+
+/* {{{ proto bool xmlrpc_server_register_introspection_callback(resource server, string function)
+   Register a PHP function to generate documentation */
+PHP_FUNCTION(xmlrpc_server_register_introspection_callback)
+{
+       zval **method_name, **handle, *method_name_save;
+       int type;
+       xmlrpc_server_data* server;
+
+       if (ZEND_NUM_ARGS() != 2 || (zend_get_parameters_ex(2, &handle, &method_name) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       server = zend_list_find(Z_LVAL_PP(handle), &type);
+
+       if(type == le_xmlrpc_server) {
+               /* save for later use */
+               MAKE_STD_ZVAL(method_name_save);
+               *method_name_save = **method_name;
+               zval_copy_ctor(method_name_save);
+
+               /* register our php method */
+               add_zval(server->introspection_map, NULL, &method_name_save);
+
+               RETURN_BOOL(1);
+       }
+       RETURN_BOOL(0);
+}
+/* }}} */
+
+
+/* this function is itchin for a re-write */
+
+/* {{{ proto mixed xmlrpc_server_call_method(resource server, string xml, mixed user_data [, array output_options])
+   Parses XML requests and call methods */
+PHP_FUNCTION(xmlrpc_server_call_method)
+{
+       xmlrpc_callback_data data = {0};
+       XMLRPC_REQUEST xRequest;
+       STRUCT_XMLRPC_REQUEST_INPUT_OPTIONS input_opts;
+       xmlrpc_server_data* server;
+       zval **rawxml, **caller_params, **handle, **output_opts = NULL;
+       int type;
+       php_output_options out;
+       int argc =ZEND_NUM_ARGS();
+       
+       if (argc < 3 || argc > 4 || (zend_get_parameters_ex(argc, &handle, &rawxml, &caller_params, &output_opts) != SUCCESS)) {
+               WRONG_PARAM_COUNT;
+       }
+       /* user output options */
+       if (argc == 3) {
+               set_output_options(&out, NULL);
+       }
+       else {
+               set_output_options(&out, *output_opts);
+       }
+
+       server = zend_list_find(Z_LVAL_PP(handle), &type);
+
+       if(type == le_xmlrpc_server) {
+               /* HACK: use output encoding for now */
+               input_opts.xml_elem_opts.encoding = utf8_get_encoding_id_from_string(out.xmlrpc_out.xml_elem_opts.encoding);
+
+               /* generate an XMLRPC_REQUEST from the raw xml input */
+               xRequest = XMLRPC_REQUEST_FromXML(Z_STRVAL_PP(rawxml), Z_STRLEN_PP(rawxml), &input_opts);
+
+               if(xRequest) {
+                       const char* methodname = XMLRPC_RequestGetMethodName(xRequest);
+                       zval **php_function;
+                       XMLRPC_VALUE xAnswer = NULL;
+                       MAKE_STD_ZVAL(data.xmlrpc_method); /* init. very important.  spent a frustrating day finding this out. */
+                       MAKE_STD_ZVAL(data.return_data);
+                       Z_TYPE_P(data.return_data) = IS_NULL;  /* in case value is never init'd, we don't dtor to think it is a string or something */
+                       Z_TYPE_P(data.xmlrpc_method) = IS_NULL;
+
+                       if (!methodname) {
+                               methodname = "";
+                       }
+            
+                       /* setup some data to pass to the callback function */
+                       Z_STRVAL_P(data.xmlrpc_method) = estrdup(methodname);
+                       Z_STRLEN_P(data.xmlrpc_method) = strlen(methodname);
+                       Z_TYPE_P(data.xmlrpc_method) = IS_STRING;
+                       data.caller_params = *caller_params;
+                       data.php_executed = 0;
+                       data.server = server;
+
+                       /* check if the called method has been previous registered */
+                       if(zend_hash_find(Z_ARRVAL_P(server->method_map),
+                              Z_STRVAL_P(data.xmlrpc_method), 
+                              Z_STRLEN_P(data.xmlrpc_method) + 1, 
+                              (void**)&php_function) == SUCCESS) {
+
+                               data.php_function = *php_function;
+                       }
+
+                       /* We could just call the php method directly ourselves at this point, but we do this 
+                        * with a C callback in case the xmlrpc library ever implements some cool usage stats,
+                        * or somesuch.
+                        */
+                       xAnswer = XMLRPC_ServerCallMethod(server->server_ptr, xRequest, &data);
+                       if(xAnswer && out.b_php_out) {
+                               zval_dtor(data.return_data);
+                               FREE_ZVAL(data.return_data);
+                               data.return_data = XMLRPC_to_PHP(xAnswer);
+                       } else if(data.php_executed && !out.b_php_out) {
+                               xAnswer = PHP_to_XMLRPC(&out, data.return_data);
+                       }
+
+                       /* should we return data as xml? */
+                       if(!out.b_php_out) {
+                               XMLRPC_REQUEST xResponse = XMLRPC_RequestNew();
+                               if(xResponse) {
+                                       char *outBuf = 0;
+                                       int buf_len = 0;
+
+                                       /* automagically determine output serialization type from request type */
+                                       if (out.b_auto_version) { 
+                                               XMLRPC_REQUEST_OUTPUT_OPTIONS opts = XMLRPC_RequestGetOutputOptions(xRequest);
+                                               if (opts) {
+                                                       out.xmlrpc_out.version = opts->version;
+                                               }
+                                       }
+                                       /* set some required request hoojum */
+                                       XMLRPC_RequestSetOutputOptions(xResponse, &out.xmlrpc_out);
+                                       XMLRPC_RequestSetRequestType(xResponse, xmlrpc_request_response);
+                                       XMLRPC_RequestSetData(xResponse, xAnswer);
+                                       XMLRPC_RequestSetMethodName(xResponse, methodname);
+
+                                       /* generate xml */
+                                       outBuf = XMLRPC_REQUEST_ToXML(xResponse, &buf_len);
+                                       if(outBuf) {
+                                               RETVAL_STRINGL(outBuf, buf_len, 1);
+                                               free(outBuf);
+                                       }
+                                       /* cleanup after ourselves.  what a sty! */
+                                       XMLRPC_RequestFree(xResponse, 0);
+                               }
+                       } else { /* or as native php types? */
+                               *return_value = *data.return_data;
+                               zval_copy_ctor(return_value);
+                       }
+
+                       /* cleanup after ourselves.  what a sty! */
+                       zval_dtor(data.xmlrpc_method);
+                       FREE_ZVAL(data.xmlrpc_method);
+                       zval_dtor(data.return_data);
+                       FREE_ZVAL(data.return_data);
+
+                       if(xAnswer) {
+                               XMLRPC_CleanupValue(xAnswer);
+                       }
+
+                       XMLRPC_RequestFree(xRequest, 1);
+               }
+       }
+}
+/* }}} */
+
+
+/* {{{ proto int xmlrpc_server_add_introspection_data(resource server, array desc)
+   Adds introspection documentation  */
+PHP_FUNCTION(xmlrpc_server_add_introspection_data)
+{
+       zval **handle, **desc;
+       int type;
+       xmlrpc_server_data* server;
+
+       if (ZEND_NUM_ARGS() != 2 || (zend_get_parameters_ex(2, &handle, &desc) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       server = zend_list_find(Z_LVAL_PP(handle), &type);
+
+       if (type == le_xmlrpc_server) {
+               XMLRPC_VALUE xDesc = PHP_to_XMLRPC(NULL, *desc);
+               if (xDesc) {
+                       int retval = XMLRPC_ServerAddIntrospectionData(server->server_ptr, xDesc);
+                       XMLRPC_CleanupValue(xDesc);
+                       RETURN_LONG(retval);
+               }
+       }
+       RETURN_LONG(0);
+}
+/* }}} */
+
+
+/* {{{ proto array xmlrpc_parse_method_descriptions(string xml)
+   Decodes XML into a list of method descriptions */
+PHP_FUNCTION(xmlrpc_parse_method_descriptions)
+{
+       zval **arg1, *retval;
+
+       if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &arg1) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_string_ex(arg1);
+
+       if(return_value_used) {
+               STRUCT_XMLRPC_ERROR err = {0};
+               XMLRPC_VALUE xVal = XMLRPC_IntrospectionCreateDescription(Z_STRVAL_PP(arg1), &err);
+               if(xVal) {
+                       retval = XMLRPC_to_PHP(xVal);
+
+                       if(retval) {
+                               *return_value = *retval;
+                               zval_copy_ctor(return_value);
+                       }
+                       /* dust, sweep, and mop */
+                       XMLRPC_CleanupValue(xVal);
+               } else {
+                       /* could not create description */
+                       if(err.xml_elem_error.parser_code) {
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "xml parse error: [line %ld, column %ld, message: %s] Unable to create introspection data", 
+                               err.xml_elem_error.column, err.xml_elem_error.line, err.xml_elem_error.parser_error);
+                       } else {
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid xml structure. Unable to create introspection data");
+                       }
+
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "xml parse error. no method description created");
+               }
+       }
+}
+/* }}} */
+
+
+/************
+* type data *
+************/
+
+#define XMLRPC_TYPE_COUNT 10
+#define XMLRPC_VECTOR_TYPE_COUNT 4
+#define TYPE_STR_MAP_SIZE (XMLRPC_TYPE_COUNT + XMLRPC_VECTOR_TYPE_COUNT)
+
+/* return a string matching a given xmlrpc type */
+static const char** get_type_str_mapping(void)
+{
+   static const char* str_mapping[TYPE_STR_MAP_SIZE];
+   static int first = 1;
+   if (first) {
+      /* warning. do not add/delete without changing size define */
+      str_mapping[xmlrpc_none]     = "none";
+      str_mapping[xmlrpc_empty]    = "empty";
+      str_mapping[xmlrpc_nil]      = "nil";
+      str_mapping[xmlrpc_base64]   = "base64";
+      str_mapping[xmlrpc_boolean]  = "boolean";
+      str_mapping[xmlrpc_datetime] = "datetime";
+      str_mapping[xmlrpc_double]   = "double";
+      str_mapping[xmlrpc_int]      = "int";
+      str_mapping[xmlrpc_string]   = "string";
+      str_mapping[xmlrpc_vector]   = "vector";
+      str_mapping[XMLRPC_TYPE_COUNT + xmlrpc_vector_none]   = "none";
+      str_mapping[XMLRPC_TYPE_COUNT + xmlrpc_vector_array]  = "array";
+      str_mapping[XMLRPC_TYPE_COUNT + xmlrpc_vector_mixed]  = "mixed";
+      str_mapping[XMLRPC_TYPE_COUNT + xmlrpc_vector_struct] = "struct";
+      first = 0;
+   }
+   return (const char**)str_mapping;
+}
+
+/* map an xmlrpc type to a string */
+const char* xmlrpc_type_as_str(XMLRPC_VALUE_TYPE type, XMLRPC_VECTOR_TYPE vtype)
+{
+   const char** str_mapping = get_type_str_mapping();
+
+   if (vtype == xmlrpc_vector_none) {
+      return str_mapping[type];
+   } else {
+      return str_mapping[XMLRPC_TYPE_COUNT + vtype];
+   }
+}
+
+/* map a string to an xmlrpc type */
+XMLRPC_VALUE_TYPE xmlrpc_str_as_type(const char* str)
+{
+   const char** str_mapping = get_type_str_mapping();
+   int i;
+
+   if (str) {
+      for (i = 0; i < XMLRPC_TYPE_COUNT; i++) {
+         if (!strcmp(str_mapping[i], str)) {
+            return (XMLRPC_VALUE_TYPE) i;
+         }
+      }
+   }
+   return xmlrpc_none;
+}
+
+/* map a string to an xmlrpc vector type */
+XMLRPC_VECTOR_TYPE xmlrpc_str_as_vector_type(const char* str)
+{
+   const char** str_mapping = get_type_str_mapping();
+   int i;
+
+   if (str) {
+      for (i = XMLRPC_TYPE_COUNT; i < TYPE_STR_MAP_SIZE; i++) {
+         if (!strcmp(str_mapping[i], str)) {
+            return (XMLRPC_VECTOR_TYPE) (i - XMLRPC_TYPE_COUNT);
+         }
+      }
+   }
+   return xmlrpc_none;
+}
+
+
+/* set a given value to a particular type. 
+ * note: this only works on strings, and only for date and base64,
+ *       which do not have native php types. black magic lies herein.
+ */
+int set_zval_xmlrpc_type(zval* value, XMLRPC_VALUE_TYPE newtype)
+{
+   int bSuccess = FAILURE;
+   TSRMLS_FETCH();
+
+   /* we only really care about strings because they can represent
+    * base64 and datetime.  all other types have corresponding php types
+    */
+   if (Z_TYPE_P(value) == IS_STRING) {
+      if (newtype == xmlrpc_base64 || newtype == xmlrpc_datetime) {
+         const char* typestr = xmlrpc_type_as_str(newtype, xmlrpc_vector_none);
+         zval* type;
+
+         MAKE_STD_ZVAL(type);
+
+         Z_TYPE_P(type) = IS_STRING;
+         Z_STRVAL_P(type) = estrdup(typestr);
+         Z_STRLEN_P(type) = strlen(typestr);
+
+         if(newtype == xmlrpc_datetime) {
+            XMLRPC_VALUE v = XMLRPC_CreateValueDateTime_ISO8601(NULL, value->value.str.val);
+            if(v) {
+               time_t timestamp = XMLRPC_GetValueDateTime(v);
+               if(timestamp) {
+                  pval* ztimestamp;
+
+                  MAKE_STD_ZVAL(ztimestamp);
+
+                  ztimestamp->type = IS_LONG;
+                  ztimestamp->value.lval = timestamp;
+
+                  convert_to_object(value);
+                  if(SUCCESS == zend_hash_update(Z_OBJPROP_P(value), OBJECT_TYPE_ATTR, sizeof(OBJECT_TYPE_ATTR), (void *) &type, sizeof(zval *), NULL)) {
+                     bSuccess = zend_hash_update(Z_OBJPROP_P(value), OBJECT_VALUE_TS_ATTR, sizeof(OBJECT_VALUE_TS_ATTR), (void *) &ztimestamp, sizeof(zval *), NULL);
+                  }
+               }
+               XMLRPC_CleanupValue(v);
+            }
+         }
+         else {
+            convert_to_object(value);
+            bSuccess = zend_hash_update(Z_OBJPROP_P(value), OBJECT_TYPE_ATTR, sizeof(OBJECT_TYPE_ATTR), (void *) &type, sizeof(zval *), NULL);
+         }
+      }
+   }
+   
+   return bSuccess;
+}
+
+/* return xmlrpc type of a php value */
+XMLRPC_VALUE_TYPE get_zval_xmlrpc_type(php_output_options* out, zval* value, zval** newvalue)
+{
+   XMLRPC_VALUE_TYPE type = xmlrpc_none;
+   TSRMLS_FETCH();
+   XMLRPCLS_FETCH();
+
+   if (value) {
+      switch (Z_TYPE_P(value)) {
+         case IS_NULL:
+            if (XMLRPCG(allow_null) || (out && out->b_allow_null)) {
+               type = xmlrpc_nil;
+            } else {
+               type = xmlrpc_string;
+            }
+            break;
+#ifndef BOOL_AS_LONG
+
+   /* Right thing to do, but it breaks some legacy code. */
+         case IS_BOOL:
+            type = xmlrpc_boolean;
+            break;
+#else
+         case IS_BOOL:
+#endif
+         case IS_LONG:
+         case IS_RESOURCE:
+            type = xmlrpc_int;
+            break;
+         case IS_DOUBLE:
+            type = xmlrpc_double;
+            break;
+         case IS_CONSTANT:
+            type = xmlrpc_string;
+            break;
+         case IS_STRING:
+            type = xmlrpc_string;
+            break;
+         case IS_ARRAY:
+         case IS_CONSTANT_ARRAY:
+            type = xmlrpc_vector;
+            break;
+         case IS_OBJECT:
+         {
+            zval** attr;
+            type = xmlrpc_vector;
+
+            if (zend_hash_find(Z_OBJPROP_P(value), OBJECT_TYPE_ATTR, sizeof(OBJECT_TYPE_ATTR), (void**) &attr) == SUCCESS) {
+               if (Z_TYPE_PP(attr) == IS_STRING) {
+                  type = xmlrpc_str_as_type(Z_STRVAL_PP(attr));
+               }
+            }
+            break;
+         }
+      }
+
+               /* if requested, return an unmolested (magic removed) copy of the value */
+               if (newvalue) {
+                       zval** val;
+
+                       if ((type == xmlrpc_base64 && Z_TYPE_P(value) != IS_NULL) || type == xmlrpc_datetime) {
+                               if (zend_hash_find(Z_OBJPROP_P(value), OBJECT_VALUE_ATTR, sizeof(OBJECT_VALUE_ATTR), (void**) &val) == SUCCESS) {
+                                       *newvalue = *val;
+                               }
+                       } else {
+                               *newvalue = value;
+                       }
+               }
+       }
+
+       return type;
+}
+
+
+/* {{{ proto bool xmlrpc_set_type(string value, string type)
+   Sets xmlrpc type, base64 or datetime or nil, for a PHP string value */
+PHP_FUNCTION(xmlrpc_set_type)
+{
+       zval **arg, **type;
+       XMLRPC_VALUE_TYPE vtype;
+
+       if (ZEND_NUM_ARGS() != 2 || (zend_get_parameters_ex(2, &arg, &type) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       convert_to_string_ex(type);
+       vtype = xmlrpc_str_as_type(Z_STRVAL_PP(type));
+       if (vtype != xmlrpc_none) {
+               if (set_zval_xmlrpc_type(*arg, vtype) == SUCCESS) {
+                       RETURN_TRUE;
+               }
+       } else {
+               zend_error(E_WARNING,"invalid type '%s' passed to xmlrpc_set_type()", Z_STRVAL_PP(type));
+       }
+       RETURN_FALSE;
+}
+/* }}} */
+
+/* {{{ proto string xmlrpc_get_type(mixed value)
+   Gets xmlrpc type for a PHP value. Especially useful for base64 and datetime strings */
+PHP_FUNCTION(xmlrpc_get_type)
+{
+       zval **arg;
+       XMLRPC_VALUE_TYPE type;
+       XMLRPC_VECTOR_TYPE vtype = xmlrpc_vector_none;
+
+       if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &arg) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       type = get_zval_xmlrpc_type(NULL, *arg, 0);
+       if (type == xmlrpc_vector) {
+               vtype = determine_vector_type(Z_ARRVAL_PP(arg));
+       }
+   
+       RETURN_STRING((char*) xmlrpc_type_as_str(type, vtype), 1);
+}
+/* }}} */
+
+/* {{{ proto bool xmlrpc_is_fault(array)
+   Determines if an array value represents an XMLRPC fault. */
+PHP_FUNCTION(xmlrpc_is_fault)
+{
+       zval **arg, **val;
+
+       if (ZEND_NUM_ARGS() != 1 || (zend_get_parameters_ex(1, &arg) == FAILURE)) {
+               WRONG_PARAM_COUNT;
+       }
+
+       if (Z_TYPE_PP(arg) != IS_ARRAY) {
+               php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Array argument expected");
+       } else {
+               /* The "correct" way to do this would be to call the xmlrpc
+                * library XMLRPC_ValueIsFault() func.  However, doing that
+                * would require us to create an xmlrpc value from the php
+                * array, which is rather expensive, especially if it was
+                * a big array.  Thus, we resort to this not so clever hackery.
+                */
+               if (zend_hash_find(Z_ARRVAL_PP(arg), FAULT_CODE, FAULT_CODE_LEN + 1, (void**) &val) == SUCCESS && 
+                   zend_hash_find(Z_ARRVAL_PP(arg), FAULT_STRING, FAULT_STRING_LEN + 1, (void**) &val) == SUCCESS) {
+                       RETURN_TRUE;
+               }
+       }
+
+       RETURN_FALSE;
+}
+/* }}} */
+
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
diff --git a/php/xmlrpc/xmlrpc.dsp b/php/xmlrpc/xmlrpc.dsp
new file mode 100644 (file)
index 0000000..8c455d3
--- /dev/null
@@ -0,0 +1,211 @@
+# Microsoft Developer Studio Project File - Name="xmlrpc" - Package Owner=<4>\r
+# Microsoft Developer Studio Generated Build File, Format Version 6.00\r
+# ** DO NOT EDIT **\r
+\r
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102\r
+\r
+CFG=xmlrpc - Win32 Debug_TS\r
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r
+!MESSAGE use the Export Makefile command and run\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "xmlrpc.mak".\r
+!MESSAGE \r
+!MESSAGE You can specify a configuration when running NMAKE\r
+!MESSAGE by defining the macro CFG on the command line. For example:\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "xmlrpc.mak" CFG="xmlrpc - Win32 Debug_TS"\r
+!MESSAGE \r
+!MESSAGE Possible choices for configuration are:\r
+!MESSAGE \r
+!MESSAGE "xmlrpc - Win32 Debug_TS" (based on "Win32 (x86) Dynamic-Link Library")\r
+!MESSAGE "xmlrpc - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")\r
+!MESSAGE \r
+\r
+# Begin Project\r
+# PROP AllowPerConfigDependencies 0\r
+# PROP Scc_ProjName ""\r
+# PROP Scc_LocalPath ""\r
+CPP=cl.exe\r
+MTL=midl.exe\r
+RSC=rc.exe\r
+\r
+!IF  "$(CFG)" == "xmlrpc - Win32 Debug_TS"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 1\r
+# PROP BASE Output_Dir "Debug_TS"\r
+# PROP BASE Intermediate_Dir "Debug_TS"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 1\r
+# PROP Output_Dir "Debug_TS"\r
+# PROP Intermediate_Dir "Debug_TS"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "XMLRPC_EXPORTS" /YX /FD /GZ /c\r
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "..\.." /I "..\..\main" /I "..\..\Zend" /I "..\..\TSRM" /I "libxmlrpc" /I "..\..\bundle\expat" /D HAVE_XMLRPC=1 /D "ZEND_WIN32" /D "PHP_WIN32" /D ZEND_DEBUG=1 /D ZTS=1 /D COMPILE_DL_XMLRPC=1 /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "XMLRPC_EXPORTS" /YX /FD /GZ /c\r
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32\r
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32\r
+# ADD BASE RSC /l 0x1009 /d "_DEBUG"\r
+# ADD RSC /l 0x1009 /d "_DEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LINK32=link.exe\r
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept\r
+# ADD LINK32 php5ts_debug.lib expat.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"..\..\Debug_TS/php_xmlrpc.dll" /pdbtype:sept /libpath:"..\..\Debug_TS"\r
+\r
+!ELSEIF  "$(CFG)" == "xmlrpc - Win32 Release_TS"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 0\r
+# PROP BASE Output_Dir "Release_TS"\r
+# PROP BASE Intermediate_Dir "Release_TS"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 0\r
+# PROP Output_Dir "Release_TS"\r
+# PROP Intermediate_Dir "Release_TS"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "XMLRPC_EXPORTS" /YX /FD /c\r
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\.." /I "..\..\main" /I "..\..\Zend" /I "..\..\TSRM" /I "libxmlrpc" /I "..\..\bundle\expat" /D HAVE_XMLRPC=1 /D "ZEND_WIN32" /D ZEND_DEBUG=0 /D "PHP_WIN32" /D ZTS=1 /D COMPILE_DL_XMLRPC=1 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "XMLRPC_EXPORTS" /YX /FD /c\r
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32\r
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32\r
+# ADD BASE RSC /l 0x1009 /d "NDEBUG"\r
+# ADD RSC /l 0x1009 /d "NDEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LINK32=link.exe\r
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386\r
+# ADD LINK32 php5ts.lib expat.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"..\..\Release_TS/php_xmlrpc.dll" /libpath:"..\..\Release_TS"\r
+\r
+!ENDIF \r
+\r
+# Begin Target\r
+\r
+# Name "xmlrpc - Win32 Debug_TS"\r
+# Name "xmlrpc - Win32 Release_TS"\r
+# Begin Group "Source Files"\r
+\r
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"\r
+# Begin Source File\r
+\r
+SOURCE=".\xmlrpc-epi-php.c"\r
+# End Source File\r
+# End Group\r
+# Begin Group "Header Files"\r
+\r
+# PROP Default_Filter "h;hpp;hxx;hm;inl"\r
+# Begin Source File\r
+\r
+SOURCE=.\php_xmlrpc.h\r
+# End Source File\r
+# End Group\r
+# Begin Group "Resource Files"\r
+\r
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"\r
+# End Group\r
+# Begin Group "libxmlrpc"\r
+\r
+# PROP Default_Filter ""\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\base64.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\base64.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\encodings.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\encodings.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\queue.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\queue.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\simplestring.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\simplestring.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\system_methods.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\system_methods_private.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_element.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_element.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_to_dandarpc.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_to_dandarpc.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_to_soap.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_to_soap.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_to_xmlrpc.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xml_to_xmlrpc.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xmlrpc.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xmlrpc.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xmlrpc_introspection.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xmlrpc_introspection.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xmlrpc_introspection_private.h\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\libxmlrpc\xmlrpc_private.h\r
+# End Source File\r
+# End Group\r
+# End Target\r
+# End Project\r
diff --git a/psycopg2/AUTHORS b/psycopg2/AUTHORS
new file mode 100644 (file)
index 0000000..44c77fc
--- /dev/null
@@ -0,0 +1,8 @@
+Main authors:
+  Federico Di Gregorio <fog@debian.org>
+
+For the win32 port:
+  Jason Erickson <jerickso@indian.com> (most of his changes are still in 2.0)
+
+Additional Help:
+  
diff --git a/psycopg2/LICENSE b/psycopg2/LICENSE
new file mode 100644 (file)
index 0000000..b20b282
--- /dev/null
@@ -0,0 +1,60 @@
+psycopg and the GPL
+===================
+
+psycopg 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. See file COPYING for details.
+
+As a special exception, specific permission is granted for the GPLed
+code in this distribition to be linked to OpenSSL and PostgreSQL libpq
+without invoking GPL clause 2(b).
+
+Note that the GPL was chosen to avoid proprietary adapters based on
+psycopg code. Using psycopg in a proprietary product (even bundling
+psycopg with the proprietary product) is fine as long as:
+
+ 1. psycopg is called from Python only using only the provided API
+    (i.e., no linking with C code and no C modules based on it); and
+
+ 2. all the other points of the GPL are respected (you offer a copy
+    of psycopg's source code, and so on.)
+
+Alternative licenses
+====================
+
+If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e.,
+every file inside the ZPsycopgDA directory) user the ZPL license as
+published on the Zope web site, http://www.zope.org/Resources/ZPL.
+
+Also, the following BSD-like license applies (at your option) to the
+files following the pattern psycopg/adapter*.{h,c} and
+psycopg/microprotocol*.{h,c}:
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+    claim that you wrote the original software. If you use this
+    software in a product, an acknowledgment in the product documentation
+    would be appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not
+    be misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+psycopg 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.
+
+Proprietary licenses
+====================
+
+A non-exclusive license is available for companies that want to include
+psycopg in their proprietary products without respecting the spirit of the
+GPL. The price of the license is one day of development done by the author,
+at the consulting fee he applies to his usual customers at the day of the
+request.
diff --git a/psycopg2/MANIFEST.in b/psycopg2/MANIFEST.in
new file mode 100644 (file)
index 0000000..457004c
--- /dev/null
@@ -0,0 +1,12 @@
+recursive-include psycopg *.c *.h
+recursive-include lib *.py
+recursive-include tests *.py
+recursive-include ZPsycopgDA *.py *.gif *.dtml
+recursive-include examples *.py somehackers.jpg whereareyou.jpg
+recursive-include debian *
+recursive-include doc TODO HACKING SUCCESS ChangeLog-1.x async.txt
+recursive-include scripts *.py *.sh
+include scripts/maketypes.sh scripts/buildtypes.py
+include AUTHORS README INSTALL LICENSE ChangeLog 
+include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg
+recursive-include doc *.rst *.css *.html
diff --git a/psycopg2/doc/ChangeLog-1.x b/psycopg2/doc/ChangeLog-1.x
new file mode 100644 (file)
index 0000000..dadfc1b
--- /dev/null
@@ -0,0 +1,1744 @@
+2003-07-26  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.1.7.
+
+       * ZPsycopgDA/db.py: added _cursor method that checks for self.db
+       before returning a new cursor. Should fix problem reported with
+       Zope 2.7.  
+
+2003-07-23  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c: applied notify and fileno patch from Vsevolod Lobko.
+
+2003-07-20  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_mogrify_dict): applied (slightly modofied) patch from
+       Tobias Sargeant: now .execute() accept not only dictionaries but
+       every type that has a __getitem__ method.
+
+2003-07-13  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.1.6.
+
+       * cursor.c (psyco_curs_scroll): added scroll method, patch from
+       Jason D.Hildebrand.
+
+       * typemod.c (new_psyco_quotedstringobject): discard NUL characters
+       (\0) in quoted strings (fix problem reported by Richard Taylor.)
+
+2003-07-10  Federico Di Gregorio  <fog@debian.org>
+
+       * Added python-taylor.txt in doc directory: very nice introduction
+       to DBAPI programming by Richard Taylor.
+
+2003-07-09  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_psyco_curs_execute): another MT problem exposed and
+       fixed by Sebastien Bigaret (self->keeper->status still LOCKED
+       after a fatal error during PQexec call.)
+
+2003-06-23  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.1.5.1.
+
+       * ZPsycopgDA/db.py (DB.query): stupid error making ZPsycopgDA
+       unusable fixed (else->except).
+
+2003-06-22  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.1.5 candidate.
+
+       * cursor.c (psyco_curs_copy_to): now any object with the write
+       method can be used as a copy_to target.  
+
+2003-06-20  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (psyco_curs_copy_from): applied patch to allow copy_to
+       from any object having a "readline" attribute (patch from Lex
+       Berezhny.) (psyco_curs_copy_from): another patch from Lex to make
+       psycopg raise an error on COPY FROM errors. 
+
+       * ZPsycopgDA/db.py (DB.query): if a query raise an exception,
+       first self._abort() is called to rollback current
+       "sub-transaction".  this is a backward-compatible change for
+       people that think continuing to work in the same zope transaction
+       after an exception is a Good Thing (TM).
+
+       * finally updated check_types.expected. checked by hand the
+       conversions work the right way.
+
+       * doc/examples/work.py: fixed example. note that it is a long time
+       (at least two releases) that psycopg does not END a transaction
+       initiated explicitly by the user while in autocommit mode.
+
+2003-06-19  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_mogrify_dict): fixed dictionary mogrification (patch
+       by Vsevolod Lobko.) (_psyco_curs_execute): fixed keeper status
+       trashing problem by letting only one thread at time play with
+       keeper->status (as suggested by Sebastien Bigaret.)
+
+2003-05-07  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.1.4.
+
+       * cursor.c: Added "statusmessage" attribute that holds the backend
+       message (modified lots of functions, look for self->status).
+
+2003-05-06  Federico Di Gregorio  <fog@debian.org>
+
+       * typemod.c (new_psyco_datetimeobject): moved Py_INCREF into
+       XXX_FromMx functions, to fix memory leak reported by Jim Crumpler.
+
+2003-04-11  Federico Di Gregorio  <fog@debian.org>
+
+       * module.h (PyObject_TypeCheck): fixed leak in python 2.1
+       (Guido van Rossum).
+
+2003-04-08  Federico Di Gregorio  <fog@debian.org>
+
+       * buildtypes.py (basic_types): removed LXTEXT (never user, does
+       not exists anymore.)
+
+2003-04-07  Federico Di Gregorio  <fog@debian.org>
+
+       * setup.py: added very lame setup.py script.
+
+2003-04-02  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.3. 
+
+       * psycopg.spec: Added (but modified) spec file by William
+       K. Volkman (again, this change was lost somewhere in time...)
+
+2003-04-01  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_psyco_curs_execute): psycopg was reporting everything
+       as IntegrityError; reported and fix suggested by Amin Abdulghani.
+
+2003-03-21  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (psyco_curs_fetchone): debug statements sometimes made
+       psycopg segfault: fixed by a patch by Ken Simpson.
+
+2003-03-18  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (alloc_keeper): patch from Dieter Maurer to unlock GIL
+       whaile calling PQconnectdb().
+
+2003-03-05  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.1.2.
+
+       * Applied cygwin patch from Hajime Nakagami.
+
+2003-02-25  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.1.2pre1.
+       
+       * cursor.c: added .lastrowid attribute to cursors (lastoid is
+       deprecated and will be removed sometime in the future.)
+
+       * cursor.c (begin_pgconn): implemented various isolation levels
+       (also, in abort_pgconn, commit_pgconn.)
+
+       * Added keyword parameters to psycopg.connect(): all take strings
+       (even port): database, host, port, user, password.
+       
+       * configure.in: fixed test for postgres version > 7.2.
+
+       * cursor.c (_psyco_curs_execute): removed if on pgerr in default
+       case (if we enter default pgerr can't be one of the cased ones.)
+       Also applied slightly modified patch from  William K. Volkman.
+
+2003-02-24  Federico Di Gregorio  <fog@debian.org>
+
+       * Merged in changes from 1.0.15.1 (see below for merged
+       ChangeLog.)
+
+2003-02-14  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.15.1.
+
+       * cursor.c (_mogrify_fmt): in some cases we where removing one
+       character too much from the format string, resulting in BIG BAD
+       BUG. <g> Fixed.
+
+2003-02-13  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.15. <g>
+       
+       * connection.c (_psyco_conn_close): now call dispose_pgconn on all
+       cursors, to make sure all phisical connections to the db are
+       closed (problem first reported by Amin Abdulghani.)
+
+       * DBAPI-2.0 fixed mainly due to Stuart Bishop:
+         - cursor.c (psyco_curs_setinputsizes): removed PARSEARGS, as
+           this method does nothing.
+         - cursor.c (psyco_curs_setoutputsize): .setoutputsize was
+           spelled .setoutputsizes! fixed. Also removed PARSEARGS, as this
+           method does nothing.
+
+2003-02-12  Federico Di Gregorio  <fog@debian.org>
+
+       * module.h (Dprintf): check on __APPLE__ to avoid variadic macros
+       on macos x (as reported by Stuart Bishop, btw, why gcc seems to
+       not support them on macos?)
+
+       * cursor.c (_mogrify_fmt): non-alphabetic characters are dropped
+       after the closing ")" until a real alphabetic, formatting one is
+       found. (Fix bug reported by Randall Randall.)
+
+2003-02-05  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_INTERVAL_cast): patched again to take into
+       account leading zeroes.
+
+2003-02-02  Federico Di Gregorio  <fog@debian.org>
+
+       * Makefile.pre.in: applied patch from Albert Chin-A-Young to
+       define BLDSHARED.
+
+       * README: added explicit permission to link with OpenSSL.
+
+2003-01-30  Federico Di Gregorio  <fog@debian.org>
+
+       * config.h.in: applied patch from Albert Chin-A-Young to fix
+       asprintf prototype.
+
+2003-01-29  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_mogrify_seq): fixed little refcount leak, as
+       suggested by Yves Bastide.
+
+2003-01-24  Federico Di Gregorio  <fog@debian.org>
+
+       * Merged-in changes from 1.0.14.2 (emacs diff mode is great..)
+
+       * Release 1.0.14.2.
+
+       * ZPsycopgDA/db.py (DB.query): back to allowing up to 1000 db
+       errors before trying to reopen the connection by ourselves.
+       
+       * ZPsycopgDA/db.py: a false (None preferred, 0 allowed) max_rows
+       value now means "fetch all results".
+
+2003-01-22  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (psyco_curs_fetchone): fixed little memory leak
+       reported by Dieter Maurer.
+
+2003-01-20  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py (DB.tables/columns): added registration with
+       Zope's transaction machinery.
+
+       * Release 1.0.14.1.
+
+       * ZPsycopgDA/db.py: applied some fixes and cleanups by Dieter
+       Maurer (serialization problem were no more correctly detected!)
+
+       * Release 1.0.14.
+       
+       * Merged in 1.0.14.
+
+       * Import of 1.1.1 done.
+       
+       * Moved everything to cvs HEAD.
+
+2003-01-20  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/connectionAdd.dtml: fixed typo (thanks to Andrew
+       Veitch.)
+
+       * typeobj.c (psyco_INTERVAL_cast): applied patch from Karl Putland
+       to fix problems with fractional seconds.
+
+2002-12-03  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.14-pre2.
+
+       * module.h: added macro for PyObject_TypeCheck if python version <2.2.
+
+       * typeobj.c (psyco_DBAPITypeObject_coerce): added error message to
+       coercion errors.
+
+2002-12-02  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.14-pre1.
+
+       * ZPsycopgDA/db.py (DB.sortKey): added sortKey().
+       
+       * ZPsycopgDA/DA.py: applied a patch that was lost on hard disk
+       (sic), if you sent me a patch names psycopg-1.0.13.diff modifying
+       DA.py imports and want your name here, send me an email. :)
+       [btw, the patch fix the ImageFile import, getting it from Globals
+       as it is right.]
+
+       * typeobj.c (psyco_DBAPITypeObject_coerce): Fixed coerce segfault
+       by checking explicitly for all the allowed types.
+
+2002-11-25  Federico Di Gregorio  <fog@debian.org>
+
+       * doc/examples/*.py: added .rollback() to all exceptions before
+       deleteing the old table. 
+
+       * cursor.c: Apllied patch from John Goerzen (fix memory leak in
+       executemany).
+
+2002-10-25  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.13.
+
+       * connection.c (_psyco_conn_close): remove cursors from the list
+       starting from last and moving backward (as suggested by Jeremy
+       Hylton; this is not such a big gain because python lists are
+       *linked* lists, but not removing the element 0 makes the code a
+       little bit clear.)
+
+       * cursor.c (_psyco_curs_execute): now IntegrityError is raised
+       instead of ProgrammingError when adding NULL values to a non-NULL
+       column (suggested by Edmund Lian) and on referential integrity
+       violation (as per debian bug #165791.)
+
+       * typeobj.c (psyco_DATE_cast): now we use 999999 instead of
+       5867440 for very large (both signs) dates. This allow to re-insert
+       the DateTime object into postgresql (suggested by Zahid Malik.) 
+
+2002-09-13  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.12.
+
+       * Removed code to support COPY FROM/TO, will be added to new 1.1
+       branch to be released next week.
+
+       * cursor.c (_mogrify_seq): Fixed memory leak reported by Menno
+       Smits (values obtained by calling PySequence_GetItem are *new*
+       references!)
+
+2002-09-07  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_psyco_curs_execute): Added skeleton to support COPY
+       FROM/TO.
+
+2002-09-06  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: if libcrypt can't be found we probably are on
+       MacOS X: check for libcrypto, as suggested by Aparajita Fishman.
+
+2002-09-03  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py (DB.columns): Applied patch from Dieter Maurer
+       to allow the DA-browser to work with mixed case table names.
+
+2002-08-30  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py (cast_DateTime): Applied patch from Yury to fix
+       timestamps (before they were returned with time always set to 0.)
+
+2002-08-26  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.11.1 (to fix a %&£$"! bug in ZPsycopgDA not
+       accepting psycopg 1.0.11 as a valid version.
+
+       * Release 1.0.11.
+
+2002-08-22  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.11pre2.
+
+       * cursor.c (_psyco_curs_execute): fixed IntegrityError as reported
+       by Andy Dustman. (psyco_curs_execute): converting TypeError to
+       ProgrammingError on wrong number of % and/or aeguments. 
+
+       * doc/examples/integrity.py: added example and check for
+       IntegrityError.
+
+2002-08-08  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.11pre1.
+
+2002-08-06  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py (cast_DateTime): patched as suggested by Tom
+       Jenkins; now it shouldwork with time zones too.
+
+2002-08-01  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py (cast_DateTime): fixed problem with missing
+       AM/PM, as reported by Tom Jenkins.
+
+2002-07-23  Federico Di Gregorio  <fog@debian.org>
+
+       * Fixed buglets reported by Mike Coleman.
+
+2002-07-22  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.10.
+
+2002-07-14  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.10pre2.
+       
+       * typeobj.c (psyco_LONGINTEGER_cast): fixed bad segfault by
+       INCREFfing Py_None when it is the result of a NULL conversion.
+
+2002-07-04  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.10pre1.
+       
+       * buildtypes.py (basic_types): added TIMESTAMPTZ to the types
+       converted by the DATE builtin.
+       
+       * ZPsycopgDA/DA.py (Connection.connect): Added version check.
+
+2002-07-03  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_XXX_cast): fixed bug reported by multiple users
+       by appliying Matt patch. 
+
+2002-06-30  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py (Connection.set_type_casts): applied patch from
+       Tom Jenkins to parse dates with TZ.
+
+2002-06-20  Federico Di Gregorio  <fog@debian.org>
+
+       * Preparing for release 1.0.9.
+
+       * Makefile.pre.in (dist): now we really include psycopg.spec.
+
+2002-06-17  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py (_finish, _abort): fixed problem with
+       connection left in invalid state by applying Tom Jenkins patch.
+
+2002-06-06  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py (DB._abort): fixed exception raising after an
+       error in execute triggerer deletion of self.db.
+
+2002-05-16  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (psyco_curs_fetchone): None values passed to the
+       internal typecasters. 
+
+       * typeobj.c: added management of None to all the builtin
+       typecasters. 
+
+2002-04-29  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py (cast_Time): applied 'seconds as a float' patch
+       from Jelle.
+
+2002-04-23  Federico Di Gregorio  <fog@debian.org>
+
+        * Release 1.0.8.
+
+       * Makefile.pre.in: we now include win32 related files in the
+       distribution. 
+       
+       * connection.c (psyco_conn_destroy): fixed segfault reported by
+       Scott Leerssen (we were double calling _psyco_conn_close().)
+
+       * typemod.c (new_psyco_quotedstringobject): fixed memory stomping
+       catched by assert(); thanks to Matt Hoskins for reporting this
+       one.
+
+2002-04-22  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: grmpf. we need a VERSION file for windows, we'll
+       use it for configue and debian/rules too. 
+
+       * Integrated win32 changes from Jason Erickson. Moved his
+       Readme.txt to README.win32, removed VERSION and DATE, patched
+       source where required. Renamed HISTORY to ChangeLog.win32, hoping
+       Jason will start adding changes to the real ChangeLog file.
+
+2002-04-07  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.7.1.
+       
+       * configure.in: fixed little bug as reported by ron.
+
+2002-04-05  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.7?
+       
+       * typemod.c (new_psyco_bufferobject): fixed encoding problem (0xff
+       now encoded as \377 and not \777.) Also encoding *all* chars as
+       quoted octal values to prevent "Invalid UNICODE character sequence
+       found" errors.
+
+       * Release 1.0.7. (Real this time.) (Ok, it was a joke....)
+
+2002-04-03  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: fixed problem with postgres versions in the format
+       7.2.x (sic.)
+
+       * connection.c (psyco_conn_destroy): moved most of the destroy
+       stuff into its own function (_psyco_conn_close) and added a call
+       to it from psyco_conn_close. This should fix the "psycopg does not
+       release postgres connections on .close()" problem.
+
+2002-03-29  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.7. Delayed.
+       
+       * buildtypes.py (basic_types): added TIMESTAMPTZ postgres type to
+       the list of valid DATETIME types (incredible luck, no changes to
+       the parse are needed!)
+
+       * typeobj.c (psyco_DATE_cast): fixed wrong managment of sign in
+       infinity.
+
+2002-03-27  Federico Di Gregorio  <fog@debian.org>
+       
+       * configure.in (INSTALLOPTS): added AC_PROG_CPP test, now uses
+       AC_TRY_CPP to test for _all_ required mx includes.
+
+2002-03-19  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: added check for both pg_config.h and config.h to
+       detect postgres version.
+
+       * cursor.c: now None values are correctly handled when the format
+       string is not %s but %d, etc.
+
+2002-03-08  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py: added MessageDialog import suggested by
+       Guido.
+
+2002-03-07  Federico Di Gregorio  <fog@debian.org>
+
+       * psycopg.spec: added RPM specs by William K. Volkman.
+
+       * Release 1.0.6.
+       
+       * configure.in: imported changes to allow postgres 7.2 builds from
+       unstable branch.
+
+2002-03-04  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0.5.
+
+       * applied table browser patch from Andy Dustman. 
+
+2002-02-26  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_DATE_cast): added management of infinity
+       values, this can be done in a better way, by accessing the
+       MaxDateTime and MinDateTime constants of the mx.DateTime module.
+
+2002-02-20  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: Release 1.0.4.
+
+2002-02-12  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py (DB.columns): fixed select to reenable column
+       expansion in table browsing.
+
+       * ZPsycopgDA/__init__.py: removed code that made psycopg think
+       double.  
+
+2002-02-11  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_mogrify_dict): removed Py_DECREF of Py_None,
+       references returned by PyDict_Next() are borrowed (thanks to
+       Michael Lausch for this one.)
+
+2002-02-08  Federico Di Gregorio  <fog@debian.org>
+
+       * A little bug slipped in ZPsycopgDA, releasing 1.0.3 immediately.
+
+        * Release 1.0.2.
+       
+       * tests/check_types.py (TYPES): added check for hundredths of a
+       second. 
+
+2002-02-07  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_INTERVAL_cast): patched to correct wrong
+       interpretation of hundredths of a second (patch from
+       A. R. Beresford, kudos!)
+
+2002-01-31  Federico Di Gregorio  <fog@debian.org>
+
+       * FAQ: added.
+
+2002-01-16  Federico Di Gregorio  <fog@debian.org>
+
+       * Preparing for release 1.0.1.
+       
+       * cursor.c (alloc_keeper): removed ALLOW_THREADS wrapper around
+       PQconnectdb: libpq calls crypt() that is *not* reentrant.
+
+2001-12-19  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_DBAPITypeObject_cmp): added check to simply
+       return false when two type objects are compared (type objects are
+       meaned to be compared to integers!)
+
+       * typeobj.c: fixed the memory leak reported by the guys at
+       racemi, for real this time. (added about 5 DECREFS and 2 INCREFS,
+       ouch!)
+       
+2001-12-17  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_DBAPITypeObject_cmp): fixed memory leak by
+       using PyTuple_GET_ITEM (we are sure the tuple has at least one
+       element, we built it, after all...) (many thanks to Scott Leerssen
+       for reporting the *exact line* for this one.)
+
+2001-12-13  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c: fixed memory leak due to extra strdup (thanks
+       to Leonardo Rochael Almeida.)
+
+2001-11-14  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 1.0. 
+
+       * doc/README: added explanation about guide work in progess but
+       examples working.
+
+       * debian/*: lots of changes to release 1.0 in debian too.
+
+2001-11-12  Federico Di Gregorio  <fog@debian.org>
+
+       * RELEASE-1.0: added release file, to celebrate 1.0.
+
+       * tests/zope/typecheck.zexp: regression test on types for zope.
+
+2001-11-11  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py (cast_Interval): removed typecast of interval
+       into zope DateTime. intervals are reported as strings when using
+       zope DateTime and as DateTimeDeltas when using mx.
+
+2001-11-09  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_INTERVAL_cast): complete rewrite of the
+       interval parsing code. now we don't use sscanf anymore and all is
+       done with custom code in a very tight and fast loop. 
+
+2001-11-08  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/DA.py (Connection.set_type_casts): added mx INTERVAL
+       type restore.
+
+       * ZPsycopgDA/db.py (DB.query): now we return column names even if
+       there are no rows in the result set. also, cleaned up a little bit
+       the code.
+
+2001-11-7  Federico Di Gregorio,  <fog@debian.org>
+
+       * Makefile.pre.in: fixed small problem with zcat on True64 
+       (thank you stefan.)
+
+2001-11-06  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py (DB.query): added fix for concurrent update
+       from Chris Kratz.
+
+2001-11-05  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c: now we include postgres.h if InvalidOid is still
+       undefined after all other #includes.
+
+       * README: clarified use of configure args related to python
+       versions.
+
+       * aclocal.m4: patched to work with symlinks installations (thanks
+       to Stuart Bishop.)
+
+       * cursor.c (_psyco_curs_execute): now reset the keeper's status to
+       the old value and not to BEGIN (solve problem with autocommit not
+       switching back.)
+
+2001-11-01  Federico Di Gregorio  <fog@debian.org>
+
+       * doc/examples/dt.py: added example on how to use the date and
+       time constructors. 
+
+       * Makefile.pre.in (dist-zope): removed dependencies on GNU install
+       and tar commands. Also a little general cleanup on various targets.
+
+       * ZPsycopgDA/DA.py: fixed mx.DateTime importing. 
+
+2001-10-31  Federico Di Gregorio  <fog@debian.org>
+
+       * typemod.c (psyco_xxxFromMx): fixed bug in argument parsing (we
+       weren't usigng the right type object.) 
+
+       * aclocal.m4: now builds OPT and LDFLAGS on the values of the env
+       variables instead of overwriting them.
+
+       * Makefile.pre.in (CFLAGS): removed -Wall, you can add it back at
+       compile time with OPT="-Wall" ./configure ...
+
+       * Setup.in (OPT): removed -Wall.
+
+2001-10-30  Michele Comitini <mcm@initd.net>
+
+       * module.h: ANSI C compatibility patch from Daniel Plagge.
+       
+2001-10-30  Federico Di Gregorio  <fog@debian.org>
+
+       * README: added common building problems and solutions.
+
+       * configure.in: removed check for install command, already done by
+       james's aclocal.m4 for python. removed install-sh. removed -s from
+       INSTALLOPTS.
+
+2001-10-29  Federico Di Gregorio  <fog@debian.org>
+
+       * Makefile.pre.in (dist): removed examples/ directory from
+       distribution. 
+
+       * merge with cvs head. preparing to fork again on PSYCOPG-1-0 (i
+       admit BRANCH_1_0 was quite a silly name.)
+
+       * doc/examples/usercast.py: now works. 
+
+       * connection.c (curs_rollbackall): fixed little bug (exposed by
+       the deadlock below) by changing KEEPER_READY to KEEPER_READY.
+
+       * doc/examples/commit.py: deadlock problem solved, was the
+       example script, _not_ psycopg. pew... :)
+
+       * examples/*: removed the examples moved to doc/examples/.
+       
+       * doc/examples/commit.py,dictfetch.py: moved from examples/ and
+       changed to work for 1.0. unfortunately commit.py locks psycopg!!!
+
+2001-10-24  Federico Di Gregorio  <fog@debian.org>
+
+       * modified all files neede for the 1.0 release.
+
+       * configure.in (MXFLAGS): removed electric fence support.
+
+       * Makefile.pre.in (dist): now we remove CVS working files before
+       packing the tarball.
+
+        * tests: files in this directory are not coding examples, but
+       regression tests. we need a sufficient number of tests to follow
+       every single code path in psycopg at least once. first test is
+       about datatypes.
+       
+       * doc/examples: moved new example code to examples directory, old
+       tests and code samples will stay in examples/ until the manual will
+       be finished.
+
+2001-10-16  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_INTERVAL_cast): completely revised interval
+       casting code. (psyco_TIME_cast): we use the unix epoch when the
+       date is undefined. 
+
+       * cursor.c (psyco_curs_executemany): modified sanity check to
+       accept sequences of tuples too and not just dictionaries.
+
+2001-10-15  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_INTERVAL_cast): fixed bug caused by wrong
+       parsing on '1 day' (no hours, minutes and seconds.)
+
+2001-10-15  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c (_execute): use the correct cast functions even on
+       retrival of binary cursors.
+
+2001-10-12  Federico Di Gregorio  <fog@debian.org>
+
+       * typemod.c (new_psyco_bufferobject): space not quoted anymore,
+       smarter formula to calculate realloc size.
+
+       * cursor.c (psyco_curs_fetchone): removed static tuple (using
+       static variable in multithreaded code is *crazy*, why did i do it? 
+       who knows...)
+
+       * typeobj.c (psyco_init_types): exports the binary converter (will
+       be used in cursor.c:_execute.)
+
+       * typeobj.h: added export of psyco_binary_cast object.
+
+2001-10-05  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_psyco_curs_execute): added missing Py_XDECREF on
+       casts list.
+
+       * Makefile.pre.in (dist): added install-sh file to the
+       distribution. 
+
+       * replaced PyMem_DEL with PyObject_Del where necessary.
+       
+       * connection.c (psyco_conn_destroy): added missing
+       pthread_mutex_destroy on keeper lock.
+
+2001-10-01  Michele Comitini  <mcm@initd.net>
+
+       * typemod.c(new_psyco_bufferobject()): using unsigned char for
+       binary objects to avoid too many chars escaped.  A quick and
+       simple formula to avoid memory wasting and too much reallocating
+       for the converted object.  Needs _testing_, but it is faster.
+
+       * cursor.c: #include <postgres.h>
+
+       * module.h: now debugging should be active only when asked by
+       ./configure --enable-devel
+       
+2001-09-29  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (new_psyco_cursobject): added locking of connection,
+       still unsure if necessary.
+
+2001-09-26  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: changed DEBUG into PSYCOTIC_DEBUG, to allow other
+       includes (postgres.h) to use the former. better compiler checks:
+       inline, ansi, gcc specific extensions. removed MXMODULE: we don't
+       need it anymore.
+
+       * general #include cleanup, should compile on MacOS X too.
+
+       * typeobj.c (psyco_DATE_cast): uses sscanf. should be faster too. 
+       (psyco_TIME_cast): dixit.
+
+       * applied patch from Daniel Plagge (SUN cc changes.)
+       
+2001-09-22  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py (DB._finish, DB._begin): fix for the 
+       self.db == None problem.
+
+2001-09-19  Michele Comitini  <mcm@initd.net>
+
+       * typemod.c (new_psyco_bufferobject): better memory managment
+       (now it allocates only needed space dinamically).
+
+       * typeobj.c (psyco_BINARY_cast): ripped a useless check, now
+       it assumes that binary streams come out from the db correctly
+       escaped.  Should be a lot faster.
+
+2001-09-18  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_INTERVAL_cast): fixed interval conversion
+       (hours were incorrectly converted into seconds.)
+
+2001-09-17  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_mogrify_seq, _mogrify_dict): added check for None
+       value and conversion of None -> NULL (fixes bug reported by Hamish
+       Lawson.)
+
+2001-09-12  Federico Di Gregorio  <fog@debian.org>
+
+       * module.c: added handles to new date and time conversion
+       functions (see below.)
+
+       * typemod.c (psyco_XXXFromMx): added conversion functions that
+       simply wrap the mxDateTime objects instead of creating
+       them. DBAPI-2.0 extension, off-curse. 
+
+2001-09-10  Federico Di Gregorio  <fog@debian.org>
+
+       * buildtypes.py: solved hidden bug by changing from dictionary to
+       list, to maintain ordering of types. sometimes (and just
+       sometimes) the type definitions were printed unsorted, resulting
+       is psycopg initializing the type system using the type objects in
+       the wrong order. you were getting float values from an int4
+       column? be happy, this is now fixed... 
+
+       * cursor.c (psyco_curs_lastoid): added method to get oid of the
+       last inserted row (it was sooo easy, it even works...) 
+
+2001-09-08  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_INTERVAL_cast): added casting function for the
+       postgres INTERVAL and TINTERVAL types (create a DateTimeDelta
+       object.)  
+
+2001-09-05  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c: moved all calls to begin_pgconn to a single call in
+       _psyco_curs_execute, to leave the connection in a not-idle status
+       after a commit or a rollback. this should free a lot of resources
+       on the backend side. kudos to the webware-discuss mailing list
+       members and to Clark C. Evans who suggested a nice solution.
+       
+       * connection.c (curs_rollbackall, curs_commitall): removed calls
+       to begin_pgconn, see above. 
+
+       * module.c (initpsycopg): cleaned up mxDateTime importing; we now
+       use the right function from mxDateTime.h. Is not necessary anymore
+       to include our own mx headers. This makes psycopg to depend on
+       mxDateTime >= 2.0.0.
+
+2001-09-04  Federico Di Gregorio  <fog@debian.org>
+
+       * doc/*.tex: added documentation directory and skeleton of the
+       psycopg guide. 
+
+2001-09-03  Federico Di Gregorio  <fog@debian.org>
+
+       * merged in changes from HEAD (mostly mcm fixes to binary
+       objects.)
+
+       * preparing for release 0.99.6.
+
+2001-09-03  Michele Comitini  <mcm@initd.net>
+
+       * typemod.c: much faster Binary encoding routine.
+       
+       * typeobj.c: much faster Binary decoding routine.       
+
+2001-08-28  Michele Comitini  <mcm@initd.net>
+
+       * typemod.c: Working binary object to feed data to bytea type
+       fields.
+
+       * typeobj.c: Added BINARY typecast to extract data from
+       bytea type fields.
+
+       * cursor.c: Added handling for SQL binary  cursors.
+
+2001-08-3  Michele Comitini <mcm@initd.net>
+
+       * cursor.c: fixed DATESTYLE problem thanx to Steve Drees.
+
+2001-07-26  Federico Di Gregorio  <fog@debian.org>
+
+       * Makefile.pre.in: applied change suggested by Stefan H. Holek to
+       clobber and distclean targets.
+
+2001-07-23  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py: fixed little bugs exposed by multiple select
+       changes, not we correctly import ListType and we don't override
+       the type() function with a variable. 
+
+2001-07-17  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: Release 0.99.5.
+
+2001-07-12  Federico Di Gregorio  <fog@debian.org>
+
+       * debian/* fixed some little packaging problems.
+
+2001-07-11  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c, typeobj.c: removed some Py_INCREF on PyDict_SetItem
+       keys and values to avoid memory leaks.
+
+2001-07-03  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_mogrify_dict): added dictionary mogrification: all
+       Strings in the dictionary are translated into QuotedStrings. it
+       even works... (_mogrify_seq): added sequence mogrification and
+       code to automagically mogrify all strings passed to .execute(). 
+
+2001-07-02  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.99.4.
+
+       * typemod.c: added QuotedString class and methods.
+
+       * module.c: added QuotedString method to module psycopg.
+
+       * typemod.c: changed Binary objects into something usefull. now
+       the buffer object quotes the input by translatin every char into
+       its octal representation. this consumes 4x memory but guarantees
+       that even binary data containing '\0' can go into the Binary
+       object. 
+
+       * typemod.h: added definition of QuotedString object.
+       
+2001-06-28  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py, ZPsycopgDA/DABase.py: applied patch sent by
+       yury to fix little buglet. 
+
+2001-06-22  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.99.3.
+       
+       * connection.c (new_psyco_connobject): now we strdup dsn, as a fix
+       for the problem reported by Jack Moffitt.
+
+       * Ok, this will be the stable branch from now on...
+
+       * Merged in stuff from 0.99.3. About to re-branch with a better
+       name (BRANCH_1_0)
+
+2001-06-20  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.99.3. Showstoppers for 1.0 are:
+           - documentation
+           - mxDateTime module loading
+           - bug reported by Yury.
+       
+       * Integrated patches from Michele:
+           - searching for libcrypt in configure now works
+           - removed memory leak in asprintf.c
+
+2001-06-15  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/__init__.py (initialize): applied patch from Jelle to
+       resolve problem with Zope 2.4.0a1.
+
+2001-06-14  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: added code to check for missing functions (only
+       asprintf at now.)
+
+       * asprintf.c: added compatibility code for oses that does not have
+       the asprintf() function.
+
+2001-06-10  Federico Di Gregorio  <fog@debian.org>
+
+       * Branched PSYCOPG_0_99_3. Development will continue on the cvs
+       HEAD, final adjustements and bugfixing should go to this newly
+       created branch.
+
+2001-06-08  Michele Comitini  <mcm@initd.net>
+
+       * ZPsycopgDA/DA.py: DateTime casts simplified and corrected
+       as suggested by Yury.
+
+2001-06-05  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.99.2.
+
+       * Makefile.pre.in (dist): added typemod.h and typemod.c to
+       distribution.
+       
+       * cursor.c (commit_pgconn, abort_pgconn, begin_pgconn): resolved
+       segfault reported by Andre by changing PyErr_SetString invokations
+       into pgconn_set_critical. the problem was that the python
+       interpreter simply segfaults when we touch its internal data (like
+       exception message) inside an ALLOW_THREADS wrapper.
+
+       * now that we are 100% DBAPI-2.0 compliant is time for the
+       one-dot-o release (at last!) Para-pa-pa! This one is tagged
+       PSYCOPG_0_99_1 but you can call it 1.0pre1, if you better like. 
+       (A very long text just to say 'Release 0.99.1')
+
+       * typemod.[ch]: to reach complete DBAPI-2.0 compliance we
+       introduce some new objects returned by the constructors Date(),
+       Time(), Binary(), etc. Those objects are module-to-database only,
+       the type system still takes care of the database-to-python
+       conversion.
+
+2001-06-01  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.5.5.
+
+       * module.h: better error message when trying to commit on a
+       cursor derived from serialized connection.
+       
+       * ZPsycopgDA/db.py (DB.close): now self.cursor is set to None when
+       the connection is closed.
+
+       * module.c (initpsycopg): added missing (sic) DBAPI module
+       parameters (paramstyle, apilevel, threadsafety, etc...)
+
+2001-05-24  Michele Comitini  <mcm@initd.net>
+
+       * ZPsycopgDA: Support for Zope's internal DateTime, option
+       to leave mxDateTime is available on the management interface so
+       to switch with little effort :).
+
+       * cursor.c: more aggressive cleanup of postgres results
+       to avoid the risk of memory leaking.
+
+       * typeobj.c, connection.c: deleted some Py_INCREF which
+       wasted memory.
+
+2001-05-18  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.5.4.
+
+2001-05-17  Michele Comitini  <mcm@initd.net>
+
+       * ZPsycopgDA/db.py: The connection closed by the management
+       interface of zope now raises error instead of reopening itself.
+
+       * cursor.c (psyco_curs_close):  does not try to free the cursor
+       list, as it caused a segfault on subsequent operations on the same
+       cursor.
+
+2001-05-07  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.5.3.
+       
+       * Merged in changes from me and mcm.
+
+2001-05-06  Michele Comitini  <mcm@initd.net>
+
+       * ZPsycopgDA/db.py (DB.close): Fixes a bug report by Andre
+       Shubert, which was still open since there was a tiny typo in
+       method definition.
+
+       * ZPsycopgDA/DA.py (Connection.sql_quote__): overriding standard
+       sql_quote__ method to provide correct quoting (thank to Philip
+       Mayers and Casey Duncan for this bug report).
+
+2001-05-04  Federico Di Gregorio  <fog@debian.org>
+
+       * ZPsycopgDA/db.py: added .close() method (as suffested by Andre
+       Schubert.)
+
+2001-05-04  Michele Comitini  <mcm@initd.net>
+
+       * module.h: working on a closed object now raises an
+       InterfaceError.
+
+       * ZPsycopgDA/db.py: fixed problems with dead connections detection.
+
+       * ZPsycopgDA/__init__.py: corrected SOFTWARE_HOME bug for zope
+       icon.
+
+2001-05-04  Federico Di Gregorio  <fog@debian.org>
+
+       * examples/thread_test.py: now that the serialization bug is
+       fixed, it is clear that thread_test.py is bugged! added a commit()
+       after the creation of the first table to avoid loosing it on the
+       exception raised by the CREATE of an existing table_b.
+
+2001-05-03  Federico Di Gregorio  <fog@debian.org>
+
+       * connection.c (psyco_conn_cursor): reverted to old locking
+       policy, the new caused a nasty deadlock. apparently the multiple
+       connection problem has been solved as a side-effect of the other
+       fixes... (?!)
+
+       * module.h: removes stdkeeper field from connobject, we don't need
+       it anymore.
+
+       * cursor.c (dispose_pgconn): now sets self->keeper to NULL to
+       avoid decrementing the keeper refcnt two times when the cursor is
+       first closed and then destroyed.
+
+       * connection.c (psyco_conn_cursor): fixed little bug in cursor
+       creation: now the connection is locked for the entire duration of
+       the cursor creation, to avoid a new cursor to be created with a
+       new keeper due to a delay in assigning the stdmanager cursor.
+
+       * cursor.c: added calls to pgconn_set_critical() and to
+       EXC_IFCRITICAL() where we expect problems. Still segfaults but at
+       least raise an exception...
+       
+       * cursor.c (psyco_curs_autocommit): added exception if the
+       cursor's keeper is shared between more than 1 cursor.
+
+       * module.h (EXC_IFCRITICAL): added this macro that call
+       pgconn_resolve_critical) on critical errors.
+
+       * cursor.c (alloc_keeper): added check for pgres == NULL. 
+
+       * cursor.c (psyco_curs_destroy): merged psyco_curs_destroy() and
+       psyco_curs_close(): now both call _psyco_curs_close() and destroy
+       does only some extra cleanup.
+
+2001-05-03  Michele Comitini  <mcm@initd.net>
+
+       * ZPsycopgDA/db.py: Some cleanup to bring the zope product up to
+       date with the python module.  Some bugs found thanks to Andre
+       Schubert.  Now the ZDA should rely on the new serialized version
+       of psycopg.
+
+       * cursor.c: while looking for problems in the ZDA some come out
+       here, with the inability to handle dropping connection correctly.
+       This leads to segfaults and is not fixed yet for lack of time.
+       Some problems found in cursors not willing to share the same
+       connection even if they should.  Hopefully it should be fixed
+       soon.
+
+2001-04-26  Federico Di Gregorio  <fog@debian.org>
+
+       * fixed bug reported by Andre Schubert by adding a new cast
+       function for long integers (int8 postgresql type.) at now they are
+       converted to python LongIntegers: not sure f simply convert to
+       floats.
+
+       * michele applied patch from Ivo van der Wijk to make zpsycopgda
+       behave better when INSTANCE_HOME != SOFTWARE_HOME.
+
+       * cursor.c (_psyco_curs_execute): also fill the 'columns' field.
+
+       * module.h: added a 'columns' field to cursobject, to better
+       support the new dictionary fetch functions (dictfetchone(),
+       dictfetchmany(), dictfetchall().)
+
+       * cursor.c: added the afore-mentioned functions (function names
+       are not definitive, they will follow decisions on the DBAPI SIG.)
+
+2001-04-03  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.5.1.
+
+       * mcm fixed a nasty bug by correcting a typo in module.h.
+
+2001-03-30  Federico Di Gregorio  <fog@debian.org>
+
+       * module.c (psyco_connect): added `serialized' named argument to
+       the .connect() method (takes 1 or 0 and initialize the connection
+       to the right serialization state.)
+
+       * Makefile.pre.in (dist): fixed little bug, a missing -f argument
+       to rm.
+
+       * examples/thread_test.py: removed all extension cruft.
+
+       * examples/thread_test_x.py: this one uses extensions like the
+       per-cursor commit, autocommit, etc.
+
+       * README (psycopg): added explanation on how .serialize() works. 
+
+       * connection.c (psyco_conn_serialize): added cursor serialization
+       and .serialize() method on the connection object. now we are
+       definitely DBAPI-2.0 compliant.
+
+2001-03-20  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (_psyco_curs_execute): replaced some fields in
+       description with None, as suggested on the DB-SIG ML.
+
+       * something like one hundred of little changes to allow cursors
+       share the same postgres connection. added connkeeper object and
+       pthread mutexes (both in connobject and connkeeper.) apparently it
+       works. this one will be 0.5.0, i think.
+
+2001-03-19  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c: added mutexes, they do not interact well with python
+       threads :(.
+
+2001-03-16  Michele Comitini  <mcm@initd.net>
+
+       * ZPsycopgDA/db.py (ZDA): some fixes in table browsing.
+
+2001-03-16  Federico Di Gregorio  <fog@debian.org>
+
+       * suite/tables.postgresql (TABLE_DESCRIPTIONS): fixed some typos
+       introduced by copying by hand the type values from pg_type.h.
+
+       * suite/*: added some (badly) structured code to test for
+       DBAPI-2.0 compliance.
+       
+       * cursor.c (pgconn_notice_callback): now the NOTICE processor only
+       prints NOTICEs when psycopg has been compiled with the
+       --enable-devel switch. 
+
+       * connection.c: removed 'autocommit' attribute, now is a method as
+       specified in the DBAPI-2.0 document.
+
+2001-03-15  Federico Di Gregorio  <fog@debian.org>
+
+       * connection.c (curs_commitall): splitted for cycle in two to
+       avoid the "bad snapshot" problem.
+
+2001-03-14  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.4.6.
+       
+       * cursor.c (_psyco_curs_execute): fixed nasty bug, there was an
+       free(query) left from before the execute/callproc split.
+
+       * Preparing for 0.4.6.
+
+2001-03-13  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (psyco_curs_execute): fixed some memory leaks in
+       argument parsing (the query string was not free()ed.)
+       (psyco_curs_callproc): implemented callproc() method on cursors.
+       (_psyco_curs_execute): this is the function that does the real
+       stuff for callproc() and execute().
+       (pgconn_notice_*): added translation of notices into python
+       exceptions (do we really want that?) 
+
+       * configure.in: removed some cruft (old comments and strncasecmp()
+       check)
+
+2001-03-12  Federico Di Gregorio  <fog@debian.org>
+
+       * examples/thread_test.py: added moronic argument parsing: now you
+       can give the dsn string on the command line... :(
+
+       * Release 0.4.5.
+
+2001-03-10  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (request_pgconn): added code to set datestyle to ISO on
+       new connections (many thanks to Yury <yura@vpcit.ru> for the code,
+       i changed it just a little bit to raise an exception on error.)
+
+2001-03-09  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.4.4.
+
+       * ZPsycopgDA/db.py: michele fixed a nasty bug here. 
+
+2001-03-08  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.4.3.
+
+2001-03-07  Federico Di Gregorio  <fog@debian.org>
+
+       * Makefile.pre.in (dist): typeobj_builtins.c included for people
+       without pg_type.h. if you encounter type-casting problems like
+       results cast to the wrong type, simply "rm typeobj_builtins.c" and
+       rebuild.
+
+       * typeobj.c (psyco_*_cast): removed RETURNIFNULL() macro from all
+       the builtin casting functions. (psyco_STRING_cast) does not create
+       a new string anymore, simply Py_INCREF its argument and return it.
+
+       * cursor.c (psyco_curs_fetchone): removed strdup() call. added
+       PQgetisnull() test to differentiate between real NULLs and empty
+       strings.
+
+       * Removed cursor.py (mcm, put tests in examples) and fixed some
+       typos in the dtml code.
+
+2001-03-04  Michele Comitini  <mcm@initd.net>
+
+       * examples/commit_test.py: Modifications to test argument passing
+       and string substitution to cursor functions, nothing more.
+
+       * ZPsycopgDA/db.py: now it exploits some of the good features of
+       the psycopg driver, such as connection reusage and type
+       comparison.  Code is smaller although it handles (and
+       reports) errors much better.
+
+       * cursor.c: corrected a bug that left a closed cursor in the
+       cursor list of the connection.  Now cursors are removed from the
+       lists either when they are close or when they are destroyed.
+       Better connection (TCP) error reporting and handling.
+
+
+2001-03-02  Federico Di Gregorio  <fog@debian.org>
+
+       * examples commit_test.py: added code to test autocommit.
+       
+       * examples/thread_test.py (ab_select): modified select thread to
+       test autocommit mode.
+
+       * Release 0.4.1.
+       
+       * module.h, connection.c, cursor.c: added autocommit support.
+
+2001-02-28  Federico Di Gregorio  <fog@debian.org>
+
+       * Release 0.4.
+
+2001-02-27  Michele Comitini  <mcm@initd.net>
+
+       * cursor.py: cut some unuseful code in psyco_curs_fetchmany() and
+       psyco_curs_fetchall() inserted an assert in case someting goes
+       wrong.
+
+2001-02-27  Federico Di Gregorio  <fog@debian.org>
+
+       * debian/*: various changes to build both the python module and
+       the zope db adapter in different packages (respectively
+       python-psycopg and zope-psycopgda.)  
+
+       * examples/type_test.py: better and more modular tests. 
+
+       * typeobj.c: added DATE, TIME, DATETIME, BOOLEAN, BINARY and ROWID
+       types. (RETURNIFNULL) added NULL-test to builtin conversion
+       functions (using the RETURNIFNULL macro.)
+
+2001-02-26  Federico Di Gregorio  <fog@debian.org>
+
+       * releasing 0.3 (added NEWS file.)
+
+2001-02-26  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c: fetchmany() some cleanup done.
+
+       * ZPsycopgDA/db.py, ZPsycopgDA/__init__.py, : fixes to make the
+       ZDA work some way.  WARNING WARNING WARNING the zda is still
+       alpha code, but we need some feed back on it so please give it
+       a try.
+       
+2001-02-26  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c (psyco_STRING_cast): fixed bad bad bad bug. we
+       returned the string without coping it and the type-system was more
+       than happy to Py_DECREF() it and trash the whole system. fixed at
+       last!
+
+       * module.h (Dprintf): added pid to every Dprintf() call, to
+       facilitate multi-threaded debug.
+
+2001-02-26  Michele Comitini  <mcm@initd.net>
+
+       * module.c: added code so that DateTime package need not to be
+       loaded to have mxDateTime.  This should avoid clashing with
+       DateTime from the zope distribution.
+
+       * cursor.c: setting error message in fetchmany when no more tuples
+       are left. This has to be fixed in fetch and fetchall to.
+
+2001-02-26  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: stepped up version to 0.3, ready to release
+       tomorrow morning. added check for path to DateTime module. 
+
+       * examples/usercast_test.py: generate some random boxes and
+       points, select the boxes with at least one point inside and print
+       them converting the PostgeSQL output using a user-specified cast
+       object. nice. 
+
+2001-02-24  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (psyco_curs_fetchone): now an error in the python
+       callback when typecasting results raise the correct exception.
+
+       * typeobj.c (psyco_DBAPITypeObject_call): removed extra Py_INCREF().
+
+2001-02-23  Federico Di Gregorio  <fog@debian.org>
+
+       * replaced every single instance of the string 'pgpy' with 'psyco'
+       (this was part of the general cleanup.)
+       
+       * type_test.py: added this little test program to the distribution
+       (use the new_type() method to create new instances of the type
+       objects.)  
+
+       * typeobj.c: general cleanup. fixed some bugs related to
+       refcounting (again!)
+
+       * cursor.c: general cleanup. (request_pgconn) simplified by adding
+       a support function (_extract_pgconn.)
+
+       * connection.c: general cleanup. replaced some ifs with asserts()
+       in utility functions when errors depend on programming errors and
+       not on runtime exceptions. (pgpy_conn_destroy) fixed little bug
+       when deleting available connections from the list.
+
+       * module.h: general cleanup.
+
+       * typeobj.h: general cleanup, better comments, made some function
+       declarations extern. 
+
+       * module.c: general cleanup, double-checked every function for
+       memory leaks. (pgpy_connect) removed unused variable 'connection'.
+
+2001-02-22  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c: fixed lots of bugs, added NUMBER type object. now the
+       basic tests in type_test.py work pretty well.
+
+       * cursor.c (pgpy_curs_fetchmany): fixed little bug, fetchmany()
+       reported one less row than available.
+
+       * fixed lots of bugs in typeobj.c, typeobj.h, cursor.c. apparently
+       now the type system works. it is time to clean up things a little
+       bit.
+
+2001-02-21  Federico Di Gregorio  <fog@debian.org>
+
+       * typeobj.c: separated type objects stuff from module.c
+       
+       * typeobj.h: separated type objects stuff from module.h 
+
+2001-02-19  Federico Di Gregorio  <fog@debian.org>
+
+       * cursor.c (pgpy_curs_fetchmany): now check size and adjust it to
+       be lesser or equal than the nuber of available rows.
+
+2001-02-18  Michele Comitini  <mcm@initd.net>
+
+       * module.c, module.h: added optional args maxconn and minconn to
+       connection functions
+
+       * cursor.c: better error checking in request_pgconn.
+
+       * connection.c: changed new_connect_obj to take as optional args
+       maxconn and minconn. Added the corresponding ro attributes to
+       connection objects.
+
+       * cursor.py: added some code to stress test cursor reusage.
+
+       * cursor.c: some fixes on closed cursors.
+
+       * connection.c: corrections on some assert calls.
+
+2001-02-16  Federico Di Gregorio  <fog@debian.org>
+
+       * configure.in: added --enable-priofile sqitch. changed VERSION to
+       0.2: preparing for a new release.
+
+       * cursor.c: added a couple of asserts.
+
+2001-02-16  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c, connection.c: fixed the assert problem: assert must
+       take just the value to be tested! no assignemente must be done in
+       the argument of assert() otherwise is wiped when NDEBUG is set.
+
+       * module.h: some syntax error fixed.  Error in allocating a tuple
+       corrected in macro DBAPITypeObject_NEW().
+       
+       * module.c: pgpy_DBAPITypeObject_init() is not declared static anymore.
+
+       * cursor.c: executemany() now does not create and destroy tuples
+       for each list item, so it is much faster.
+
+2001-02-14  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c:  added again Py_DECREF on the cpcon after disposing
+       it.  assert() with -DNDEBUG makes the driver segfault while it
+       should not.
+       
+
+2001-02-13  Federico Di Gregorio  <fog@debian.org>
+
+       * some of the memory leak were memprof errors, bleah. resumed some
+       old code, fixed segfault, fixed other bugs, improved speed. almost
+       ready for a new release.
+       
+       * connection.c (pgpy_conn_destroy): replaced some impossible ifs
+       with aseert()s.
+
+       * cursor.c (pgpy_curs_close): added Py_DECREF() to
+       self->descritpion to prevent a memory leak after an execute().
+
+       * connection.c (pgpy_conn_destroy): always access first element of
+       lists inside for cycles because removing items from the list makes
+       higher indices invalid.
+
+       * cursor.c (dispose_pgconn): fixed memory leak, there was a
+       missing Py_DECREF() after the addition of the C object wrapping
+       the postgresql connection to the list of available connections.
+
+       * cursor.c (dispose_pgconn): fixed another memory leak: an
+       orphaned cursor should call PQfinish() on its postgresql
+       connection because it has no python connection to give the
+       postgresql ine back.
+
+       * cursor.c (pgpy_curs_execute): added Py_DECREF() of description
+       tuple after adding it to self->description. this one fixes the
+       execute() memory leak.
+       
+       * cursor.c (pgpy_curs_fetchall): added missing Py_DECREF() on row
+       data (obtained from fetchone().) this fixes the last memory leak.
+       (thread_test.py now runs without leaking memory!)
+
+2001-02-12  Federico Di Gregorio  <fog@debian.org>
+
+       * INSTALL: removed wok cruft from head of this file.
+
+       * debian/rules: debianized the sources. python-psycopg is about to
+       enter debian. mxDateTime header locally included until the
+       maintainer of python-mxdatetime includes them in his package
+       (where they do belong.)
+
+       * autogen.sh: added option --dont-run-configure. 
+
+2001-02-09  Federico Di Gregorio  <fog@debian.org>
+
+       * module.c (initpsycopg): changed name of init function to match
+       new module name (also changed all the exception definitions.)
+
+       * README: updated psycopg description (we have a new name!)
+
+       * Ready for 0.1 release.
+
+2001-02-07  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c: now executemany takes sequences and not just
+       tuples 
+
+2001-02-07  Federico Di Gregorio  <fog@debian.org>
+
+       * Makefile.pre.in: now dist target includes test programs
+       (thread_test.py) and README and INSTALL files. 
+
+       * configure.in: changed --with-devel to --enable-devel. little
+       cosmetical fixes to the option management.
+       
+       * connection.c, module.c, cursor.c, module.h: removed 'postgres/'
+       from #include directive. it is ./configure task to find the right
+       directory.
+
+       * thread_test.py: added thread testing program.
+
+2001-02-07  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c: added code to allow threads during PQexec() calls.
+       
+       * cursor.c: added begin_pgconn to rollback() and commit()
+       so that the cursror is not in autocommit mode.
+
+       * cursor.c: added rollback() and commit() methods to cursor
+       objects.
+
+
+2001-02-07  Federico Di Gregorio  <fog@debian.org>
+
+       * connection.c (pgpy_conn_destroy): always delete item at index
+       0 and not i (because items shift in the list while deleting and
+       accessing items at len(list)/2 segfaults.)
+
+2001-02-07  Michele Comitini  <mcm@initd.net>
+
+       * connection.c: added some more checking to avoid
+       clearing of already cleared pgresults.  Calling curs_closall()
+       in conn_destroy() since cursors have to live even without
+       their parent connection, otherwise explicit deletion of
+       object referencing to those cursors can cause arbitrary code
+       to be executed.
+
+       * cursor.c: some more checking to avoid trying to close
+       already close pgconnections.
+
+2001-02-06  Federico Di Gregorio  <fog@debian.org> 
+
+       * Makefile.pre.in (CFLAGS): added -Wall to catch bad programming
+       habits. 
+
+       * cursor.c, connection.c: lots of fixes to the destroy stuff. now
+       all the cursor are destroyed *before* the connection goes away.
+
+       * cursor.c (request_pgconn): another idiot error done by not
+       replacing dsn with owner_conn->dsn. fixed.
+       (dispose_pgconn): commented if to guarantee that the connection is
+       returned to the pool of available connections.
+
+       * merged changes done by mcm.
+
+       * cursor.c: general cleanup and better debugging/error
+       messages. changed xxx_conn into xxx_pgconn where still
+       missing. some pretty big changes to the way pgconn_request()
+       allocates new connections.
+
+       * connection.c: removed all 'register' integers. obsolete, gcc
+       does a much better job optimizing cycles than a programmer
+       specifying how to use registers. 
+
+       * module.h: some general cleanup and better definition of DPrintf
+       macro. now the DEBUG variable can be specified at configure time by
+       the --with-devel switch to ./configure.
+
+2001-02-02  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c (Repository): Added functions for managing a connection
+       pool. Segfaults.
+
+       * configure.in (Repository): removed check for mxdatetime headers.
+
+2001-01-24  Federico Di Gregorio  <fog@debian.org>
+
+       * first checkout from shinning new init.d cvs.
+
+       * autotoolized build system. note that the mx headers are missing
+       from the cvs, you should get them someplace else (this is the
+       right way to do it, just require the headers in the configure
+       script.)
+
+2001-01-21  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c (Repository): commit, abort, begin functions now check
+       the right exit status of the command.
+
+       * connection.c (Repository): working commit() and rollback()
+       methods.
+
+2001-01-20  Michele Comitini  <mcm@initd.net>
+
+       * module.h (Repository): added member to cursor struct to handle
+       queries without output tuples.
+
+       * cursor.c (Repository): new working methods: executemany,
+       fetchone, fetchmany, fetchall.
+
+2001-01-18  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c (Repository): close working. destroy calling close.
+       close frees pg structures correctly.
+
+       * connection.c (Repository): close method working.  destroy seems
+       working.
+
+2001-01-17  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c (Repository): now each python cursor has its own
+       connection.  Each cursor works in a transaction block.
+
+       * connection.c (Repository): added cursor list to connection
+       object
+
+2001-01-14  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c (Repository): Beginning of code to implement cursor
+       functionalities as specified in DBA API 2.0, through the use of
+       transactions not cursors.
+
+       * connection.c (Repository): Added some error checking code for pg
+       connection (will be moved to cursor?).
+
+2001-01-13  Michele Comitini  <mcm@initd.net>
+
+       * connection.c (Repository): Added error checking in connection
+       code to fail if connection to the db could not be opened.
+
+       * module.h (Repository): New macro to help creating
+       DBAPITypeObjects.
+
+       * module.c (Repository): DBAPITypeObject __cmp__ function is now
+       very simplified using recursion.
+
+       * module.h (Repository): "DBAPIObject" changed to
+       "DBAPITypeObject".
+
+       * module.c (Repository): Fixes for coerce function of DBAPIObjects
+       by Federico Di Gregorio <fog@initd.net>.
+       (Repository): Clean up and better naming for DBAPITypeObjects.
+
+2001-01-08  Michele Comitini  <mcm@initd.net>
+
+       * module.c (Repository): Corrected the exception hierarcy
+
+       * connection.c (Repository): Begun to use the connection objects
+       of libpq
+
+2001-01-07  Michele Comitini  <mcm@initd.net>
+
+       * module.c (Repository): Added the Date/Time functions.
+
+2001-01-06  Michele Comitini  <mcm@initd.net>
+
+       * cursor.c (Repository): Skeleton of cursor interface.  All
+       methods and attributes of cursor objects are now available
+       in python.  They do nothing now.
+
+2001-01-05  Michele Comitini  <mcm@initd.net>
+
+       * module.c (Repository): Test version; module loaded with 
+       exception defined.
+       
+2001-01-05  Michele Comitini  <mcm@initd.net>
+
+       * Setup.in (Repository): Setup file.
+
+       * Makefile.pre.in (Repository): from the python source.
+
+2001-01-05  Michele Comitini  <mcm@initd.net>
+
+       * module.c: Written some code for defining exceptions.
+       
+       * module.h: Static variable for exceptions.
+       
+2001-01-04  Michele Comitini  <mcm@initd.net>
+
+       * Changelog: pre-release just a few prototypes to get started.
+       
+
diff --git a/psycopg2/doc/SUCCESS b/psycopg2/doc/SUCCESS
new file mode 100644 (file)
index 0000000..9ae91f5
--- /dev/null
@@ -0,0 +1,114 @@
+From:  Jack Moffitt <jack@xiph.org>
+To:    Psycopg Mailing List <psycopg@lists.initd.org>
+Subject:       Re: [Psycopg] preparing for 1.0
+Date:  22 Oct 2001 11:16:21 -0600      
+
+www.vorbis.com is serving from 5-10k pages per day with psycopg serving 
+data for most of that.  
+
+I plan to use it for several of our other sites, so that number will
+increase.
+
+I've never had a single problem (that wasn't my fault) besides those
+segfaults, and those are now gone as well, and I've been using psycopg
+since June (around 0.99.2?).
+
+jack.
+
+
+From:  Yury Don <gercon@vpcit.ru>
+To:    Psycopg Mailing List <psycopg@lists.initd.org>
+Subject:       Re: [Psycopg] preparing for 1.0
+Date:  23 Oct 2001 09:53:11 +0600      
+
+We use psycopg and psycopg zope adapter since fisrt public
+release (it seems version 0.4). Now it works on 3 our sites and in intranet
+applications. We had few problems, but all problems were quckly
+solved. The strong side of psycopg is that it's code is well organized
+and easy to understand. When I found a problem with non-ISO datestyle in first
+version of psycopg, it took for me 15 or 20 minutes to learn code and
+to solve the problem, even thouth my knowledge of c were poor.
+
+BTW, segfault with dictfetchall on particular data set (see [Psycopg]
+dictfetchXXX() problems) disappeared in 0.99.8pre2.
+
+-- 
+Best regards,
+Yury Don
+
+
+From:  Tom Jenkins <tjenkins@devis.com>
+To:    Federico Di Gregorio <fog@debian.org>
+Cc:    Psycopg Mailing List <psycopg@lists.initd.org>
+Subject:       Re: [Psycopg] preparing for 1.0
+Date:  23 Oct 2001 08:25:52 -0400
+       
+The US Govt Department of Labor's Office of Disability Employment
+Policy's DisabilityDirect website is run on zope and zpsycopg.
+
+
+From:  Scott Leerssen <sleerssen@racemi.com>
+To:    Federico Di Gregorio <fog@debian.org>
+Subject:       Re: [Psycopg] preparing for 1.0
+Date:  23 Oct 2001 09:56:10 -0400      
+
+Racemi's load management software infrastructure uses psycopg to handle
+complex server allocation decisions, plus storage and access of
+environmental conditions and accounting records for potentially
+thousands of servers.  Psycopg has, to this point, been the only
+Python/PostGreSQL interface that could handle the scaling required for
+our multithreaded applications.
+
+Scott
+
+
+From:  Andre Schubert <andre.schubert@geyer.kabeljournal.de>
+To:    Federico Di Gregorio <fog@debian.org>
+Cc:    Psycopg Mailing List <psycopg@lists.initd.org>
+Subject:       Re: [Psycopg] preparing for 1.0
+Date:  23 Oct 2001 11:46:07 +0200      
+
+i have changed the psycopg version to 0.99.8pre2 on all devel-machines
+and all segfaults are gone. after my holiday i wil change to 0.99.8pre2 
+or 1.0 on our production-server.
+this server contains several web-sites which are all connected to
+postgres over ZPsycopgDA.
+
+thanks as
+
+
+From:  Fred Wilson Horch <fhorch@ecoaccess.org>
+To:    <psycopg@lists.initd.org>
+Subject:       [Psycopg] Success story for psycopg
+Date:  23 Oct 2001 10:59:17 -0400
+       
+Due to various quirks of PyGreSQL and PoPy, EcoAccess has been looking for
+a reliable, fast and relatively bug-free Python-PostgreSQL interface for
+our project.
+
+Binary support in psycopg, along with the umlimited tuple size in
+PostgreSQL 7.1, allowed us to quickly prototype a database-backed file
+storage web application, which we're using for file sharing among our
+staff and volunteers.  Using a database backend instead of a file system
+allows us to easily enrich the meta-information associated with each file
+and simplifies our data handling routines.
+
+We've been impressed by the responsiveness of the psycopg team to bug
+reports and feature requests, and we're looking forward to using psycopg
+as the Python interface for additional database-backed web applications.
+
+Keep up the good work!
+-- 
+Fred Wilson Horch                       mailto:fhorch@ecoaccess.org
+Executive Director, EcoAccess           http://ecoaccess.org/
+
+
+From:  Damon Fasching <fasching@design.lbl.gov>
+To:    Michele Comitini <mcm@glisco.it>
+Cc:    fog@debian.org
+Subject:       Re: How does one create a database within Python using psycopg?
+Date:  25 Feb 2002 17:39:41 -0800
+
+[snip]
+btw I checked out 4 different Python-PostgreSQL packages.  psycopg is the
+only one which built and imported w/o any trouble!  (At least for me.)
diff --git a/psycopg2/doc/TODO b/psycopg2/doc/TODO
new file mode 100644 (file)
index 0000000..b20b276
--- /dev/null
@@ -0,0 +1,33 @@
+TODO list for psycopg 2 or later
+********************************
+
+Move items to the DONE section only after writing a test for the
+implementation. Also add a note on how the item was resolved.
+(Obviously I was joking about the test..)
+
+* Find a better way to compile the type-casting code instead of including it
+   in typecast.c directy. (Including is not that bad, but the need to touch
+   psycopg/typecast.c every time is bad bad bad.)
+
+* executemany() should _not_ take the async flag, remove it and force multiple
+   queries to be synchronous.
+
+* Fix all the docstrings.
+
+* Support the protocols API fully.
+
+* Unify the common code in typecast_datetime.c and typecast_mxdatetime.c.
+
+* Port typecasters to new-style classes.
+
+* Write a complete postgresql<->python encodings table.
+
+* Implement binary typecasters (should be easy, but it will take time.)
+
+DONE
+====
+
+* Convert type-casters to new-style types in Python 2.2+.
+
+* callproc() never worked, fix it or remove it and raise right exception.
+   [Removed callproc code, now an exception is raised.]
diff --git a/psycopg2/doc/api-screen.css b/psycopg2/doc/api-screen.css
new file mode 100644 (file)
index 0000000..22e8f7e
--- /dev/null
@@ -0,0 +1,138 @@
+/* Based on the Epydoc "default.css"\r
+** with some missing reST-related classes\r
+** and Python syntax support (from SilverCity)\r
+*/\r
+\r
+/* Body color */ \r
+body               { background: #ffffff; color: #000000; } \r
\r
+/* Tables */ \r
+table.summary, table.details, table.index\r
+                   { background: #e8f0f8; color: #000000; } \r
+tr.summary, tr.details, tr.index\r
+                   { background: #70b0f0; color: #000000;  \r
+                     text-align: left; font-size: 120%; } \r
+tr.group           { background: #c0e0f8; color: #000000;\r
+                     text-align: left; font-size: 120%;\r
+                     font-style: italic; } \r
+\r
+/* Documentation page titles */\r
+h2.module          { margin-top: 0.2em; }\r
+h2.class           { margin-top: 0.2em; }\r
\r
+/* Headings */\r
+h1.heading         { font-size: +140%; font-style: italic;\r
+                     font-weight: bold; }\r
+h2.heading         { font-size: +125%; font-style: italic;\r
+                     font-weight: bold; }\r
+h3.heading         { font-size: +110%; font-style: italic;\r
+                     font-weight: normal; }\r
+                    \r
+/* Base tree */\r
+pre.base-tree      { font-size: 80%; margin: 0; }\r
+\r
+/* TOC */\r
+p.toc { margin: 0; }\r
+\r
+/* Details Sections */\r
+table.func-details { background: #e8f0f8; color: #000000;\r
+                     border: 2px groove #c0d0d0;\r
+                     padding: 0 1em 0 1em; margin: 0.4em 0 0 0; }\r
+h3.func-detail     { background: transparent; color: #000000;\r
+                     margin: 0 0 1em 0; }\r
+\r
+table.var-details  { background: #e8f0f8; color: #000000;\r
+                     border: 2px groove #c0d0d0;\r
+                     padding: 0 1em 0 1em; margin: 0.4em 0 0 0; }\r
+h3.var-details     { background: transparent; color: #000000;\r
+                     margin: 0 0 1em 0; }\r
+\r
+/* Function signatures */\r
+.sig               { background: transparent; color: #000000;\r
+                     font-weight: bold; }  \r
+.sig-name          { background: transparent; color: #006080; }  \r
+.sig-arg, .sig-kwarg, .sig-vararg\r
+                   { background: transparent; color: #008060; }  \r
+.sig-default       { background: transparent; color: #602000; }  \r
+.summary-sig       { background: transparent; color: #000000; }  \r
+.summary-sig-name  { background: transparent; color: #204080; }\r
+.summary-sig-arg, .summary-sig-kwarg, .summary-sig-vararg\r
+                   { background: transparent; color: #008060; }  \r
+\r
+/* Doctest blocks */\r
+.py-src            { background: transparent; color: #000000; }\r
+.py-prompt         { background: transparent; color: #005050;\r
+                     font-weight: bold;}\r
+.py-string         { background: transparent; color: #006030; }\r
+.py-comment        { background: transparent; color: #003060; }\r
+.py-keyword        { background: transparent; color: #600000; }\r
+.py-output         { background: transparent; color: #404040; }\r
+div.code-block,\r
+pre.literal-block,\r
+pre.doctestblock   { background: #f4faff; color: #000000; \r
+                     padding: .5em; margin: 1em;\r
+                     border: 1px solid #708890; }\r
+table pre.doctestblock\r
+                   { background: #dce4ec; color: #000000; \r
+                     padding: .5em; margin: 1em;\r
+                     border: 1px solid #708890; }\r
+div.code-block     { font-family: monospace; }\r
+\r
+/* Variable values */\r
+pre.variable       { background: #dce4ec; color: #000000;\r
+                     padding: .5em; margin: 0;\r
+                     border: 1px solid #708890; }\r
+.variable-linewrap { background: transparent; color: #604000; }\r
+.variable-ellipsis { background: transparent; color: #604000; }\r
+.variable-quote    { background: transparent; color: #604000; }\r
+.re                { background: transparent; color: #000000; }\r
+.re-char           { background: transparent; color: #006030; }\r
+.re-op             { background: transparent; color: #600000; }\r
+.re-group          { background: transparent; color: #003060; }\r
+.re-ref            { background: transparent; color: #404040; }\r
+\r
+/* Navigation bar */ \r
+table.navbar       { background: #a0c0ff; color: #0000ff;\r
+                     border: 2px groove #c0d0d0; }\r
+th.navbar          { background: #a0c0ff; color: #0000ff; } \r
+th.navselect       { background: #70b0ff; color: #000000; } \r
+.nomargin          { margin: 0; }\r
+\r
+/* Links */ \r
+a:link             { background: transparent; color: #0000ff; }  \r
+a:visited          { background: transparent; color: #204080; }  \r
+a.navbar:link      { background: transparent; color: #0000ff; \r
+                     text-decoration: none; }  \r
+a.navbar:visited   { background: transparent; color: #204080; \r
+                     text-decoration: none; }  \r
+\r
+/* Admonitions */\r
+div.warning,\r
+div.note                { background-color: #c0e0f8;\r
+                          border: thin solid black;\r
+                          padding: 1em;\r
+                          margin-left: 1em;\r
+                          margin-right: 1em; }\r
+div.warning .first,\r
+div.note .first      { font-family: sans-serif;\r
+                          font-size: 110%;\r
+                          margin-right: 0.5em; }\r
+\r
+/* Lists */\r
+ul { margin-top: 0; }\r
+\r
+/* Python syntax */\r
+.p_character { color: olive; }\r
+.p_classname { color: blue; font-weight: bold; }\r
+.p_commentblock {color: gray; font-style: italic; }\r
+.p_commentline { color: green; font-style: italic; }\r
+.p_default {}\r
+.p_defname { color: #009999; font-weight: bold; }\r
+.p_identifier { color: black; }\r
+.p_number { color: #009999; }\r
+.p_operator { color: black; }\r
+.p_string { color: #7F007F; }\r
+.p_stringeol { color: #7F007F; }\r
+.p_triple { color: #7F0000; }\r
+.p_tripledouble { color: #7F0000; }\r
+.p_word { color: navy; font-weight: bold; }\r
diff --git a/psycopg2/doc/async.txt b/psycopg2/doc/async.txt
new file mode 100644 (file)
index 0000000..518d5fe
--- /dev/null
@@ -0,0 +1,67 @@
+psycopg asynchronous API
+************************
+
+** Important: async quaeries are not enabled for 2.0 **
+
+Program code can initiate an asynchronous query by passing an 'async=1' flag
+to the .execute() method. A very simple example, from the connection to the
+query:
+
+    conn = psycopg.connect(database='test')
+    curs = conn.cursor()
+    curs.execute("SEECT * from test WHERE fielda > %s", (1971,), async=1)
+    
+From then on any query on other cursors derived from the same connection is
+doomed to fail (and raise an exception) until the original cursor (the one
+executing the query) complete the asynchronous operation. This can happen in
+a number of different ways:
+
+    1) one of the .fetchXXX() methods is called, effectively blocking untill
+       data has been sent from the backend to the client, terminating the
+       query.
+       
+    2) .cancel() is called. This method tries to abort the current query and
+       will block until the query is aborted or fully executed. The return
+       value is True if the query was successfully aborted or False if it
+       was executed. Query result are discarded in both cases.
+       
+    3) .execute() is called again on the same cursor (.execute() on a
+       different cursor will simply raise an exception.) This waits for the
+       complete execution of the current query, discard any data and execute
+       the new one.
+
+Note that calling .execute() two times in a row will not abort the former
+query and will temporarily go to synchronous mode until the first of the two
+queries is executed.
+
+Cursors now have some extra methods that make them usefull during
+asynchronous queries:
+
+    .fileno()
+      Returns the file descriptor associated with the current connection and
+      make possible to use a cursor in a context where a file object would be
+      expected (like in a select() call.)
+
+    .isbusy()
+      Returns True if the backend is still processing the query or false if
+      data is ready to be fetched (by one of the .fetchXXX() methods.)
+      
+A code snippet that shows how to use the cursor object in a select() call:
+
+    import psycopg
+    import select
+        
+    conn = psycopg.connect(database='test')
+    curs = conn.cursor()
+    curs.execute("SEECT * from test WHERE fielda > %s", (1971,), async=1)
+
+    # wait for input with a maximum timeout of 5 seconds
+    query_ended = False
+    while not query_ended:
+        rread, rwrite, rspec = select([cursor, another_file], [], [], 5)
+       if not cursor.isbusy():
+          query_ended = True
+       # manage input from other sources like other_file, etc.
+    print "Query Results:"
+    for row in cursor:
+        print row
diff --git a/psycopg2/doc/extensions.html b/psycopg2/doc/extensions.html
new file mode 100644 (file)
index 0000000..cb71200
--- /dev/null
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="Docutils 0.3.9: http://docutils.sourceforge.net/" />
+<title>psycopg 2 extensions to the DBAPI 2.0</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="psycopg-2-extensions-to-the-dbapi-2-0">
+<h1 class="title">psycopg 2 extensions to the DBAPI 2.0</h1>
+<p>This document is a short summary of the extensions built in psycopg 2.0.x over
+the standard <a class="reference" href="http://www.python.org/peps/pep-0249.html">Python Database API Specification 2.0</a>, usually called simply
+DBAPI-2.0 or even PEP-249.  Before reading on this document please make sure
+you already know how to program in Python using a DBAPI-2.0 compliant driver:
+basic concepts like opening a connection, executing queries and commiting or
+rolling back a transaction will not be explained but just used.</p>
+<p>Many objects and extension functions are defined in the <a class="reference" href="api/public/psycopg2.extensions-module.html"><tt class="docutils literal"><span class="pre">psycopg2.extensions</span></tt></a>
+module.</p>
+<div class="section" id="connection-and-cursor-factories">
+<h1><a name="connection-and-cursor-factories">Connection and cursor factories</a></h1>
+<p>psycopg 2 exposes two new-style classes that can be sub-classed and expanded to
+adapt them to the needs of the programmer: <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html"><tt class="docutils literal"><span class="pre">cursor</span></tt></a> and <a class="reference" href="api/private/psycopg2._psycopg.connection-class.html"><tt class="docutils literal"><span class="pre">connection</span></tt></a>.  The
+<a class="reference" href="api/private/psycopg2._psycopg.connection-class.html"><tt class="docutils literal"><span class="pre">connection</span></tt></a> class is usually sub-classed only to provide a . <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html"><tt class="docutils literal"><span class="pre">cursor</span></tt></a> is much
+more interesting, because it is the class where query building, execution and
+result type-casting into Python variables happens.</p>
+<div class="section" id="row-factories">
+<h2><a name="row-factories">Row factories</a></h2>
+</div>
+<div class="section" id="tzinfo-factories">
+<h2><a name="tzinfo-factories">tzinfo factories</a></h2>
+</div>
+</div>
+<div class="section" id="setting-transaction-isolation-levels">
+<h1><a name="setting-transaction-isolation-levels">Setting transaction isolation levels</a></h1>
+<p>psycopg2 connection objects hold informations about the PostgreSQL <a class="reference" href="http://www.postgresql.org/docs/8.1/static/transaction-iso.html">transaction
+isolation level</a>.  The current transaction level can be read from the
+<a class="reference" href="api/private/psycopg2._psycopg.connection-class.html#isolation_level"><tt class="docutils literal"><span class="pre">.isolation_level</span></tt></a> attribute.  The default isolation level is <tt class="docutils literal"><span class="pre">READ</span>
+<span class="pre">COMMITTED</span></tt>.  A different isolation level con be set through the
+<a class="reference" href="api/private/psycopg2._psycopg.connection-class.html#set_isolation_level"><tt class="docutils literal"><span class="pre">.set_isolation_level()</span></tt></a> method.  The level can be set to one of the following
+constants, defined in <a class="reference" href="api/public/psycopg2.extensions-module.html"><tt class="docutils literal"><span class="pre">psycopg2.extensions</span></tt></a>:</p>
+<dl class="docutils">
+<dt><a class="reference" href="api/public/psycopg2.extensions-module.html#ISOLATION_LEVEL_AUTOCOMMIT"><tt class="docutils literal"><span class="pre">ISOLATION_LEVEL_AUTOCOMMIT</span></tt></a></dt>
+<dd>No transaction is started when command are issued and no
+<a class="reference" href="api/private/psycopg2._psycopg.connection-class.html#commit"><tt class="docutils literal"><span class="pre">.commit()</span></tt></a>/<a class="reference" href="api/private/psycopg2._psycopg.connection-class.html#rollback"><tt class="docutils literal"><span class="pre">.rollback()</span></tt></a> is required.  Some PostgreSQL command such as
+<tt class="docutils literal"><span class="pre">CREATE</span> <span class="pre">DATABASE</span></tt> can't run into a transaction: to run such command use
+<a class="reference" href="api/private/psycopg2._psycopg.connection-class.html#set_isolation_level"><tt class="docutils literal"><span class="pre">.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)</span></tt></a>.</dd>
+<dt><a href="#id2" name="id3"><span class="problematic" id="id3">`ISOLATION_LEVEL_READ_COMMITTED`</span></a></dt>
+<dd><div class="first system-message" id="id2">
+<p class="system-message-title">System Message: <a name="id2">ERROR/3</a> (<tt class="docutils">../doc/extensions.rst</tt>, line 54); <em><a href="#id3">backlink</a></em></p>
+Can't find 'ISOLATION_LEVEL_READ_COMMITTED' in any provided module.</div>
+<p class="last">This is the default value.  A new transaction is started at the first
+<a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#execute"><tt class="docutils literal"><span class="pre">.execute()</span></tt></a> command on a cursor and at each new <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#execute"><tt class="docutils literal"><span class="pre">.execute()</span></tt></a> after a
+<a class="reference" href="api/private/psycopg2._psycopg.connection-class.html#commit"><tt class="docutils literal"><span class="pre">.commit()</span></tt></a> or a <a class="reference" href="api/private/psycopg2._psycopg.connection-class.html#rollback"><tt class="docutils literal"><span class="pre">.rollback()</span></tt></a>.  The transaction runs in the PostgreSQL
+<tt class="docutils literal"><span class="pre">READ</span> <span class="pre">COMMITTED</span></tt> isolation level.</p>
+</dd>
+<dt><a class="reference" href="api/public/psycopg2.extensions-module.html#ISOLATION_LEVEL_SERIALIZABLE"><tt class="docutils literal"><span class="pre">ISOLATION_LEVEL_SERIALIZABLE</span></tt></a></dt>
+<dd>Transactions are run at a <tt class="docutils literal"><span class="pre">SERIALIZABLE</span></tt> isolation level.</dd>
+</dl>
+</div>
+<div class="section" id="adaptation-of-python-values-to-sql-types">
+<h1><a name="adaptation-of-python-values-to-sql-types">Adaptation of Python values to SQL types</a></h1>
+<p>psycopg2 casts Python variables to SQL literals by type.  Standard Python types
+are already adapted to the proper SQL literal.</p>
+<p>Example: the Python function:</p>
+<pre class="literal-block">
+curs.execute(&quot;&quot;&quot;INSERT INTO atable (anint, adate, astring)
+                 VALUES (%s, %s, %s)&quot;&quot;&quot;,
+             (10, datetime.date(2005, 11, 18), &quot;O'Reilly&quot;))
+</pre>
+<p>is converted into the SQL command:</p>
+<pre class="literal-block">
+INSERT INTO atable (anint, adate, astring)
+ VALUES (10, '2005-11-18', 'O''Reilly');
+</pre>
+<p>Named arguments are supported too with <tt class="docutils literal"><span class="pre">%(name)s</span></tt> placeholders. Notice that:</p>
+<blockquote>
+<ul class="simple">
+<li>The Python string operator <tt class="docutils literal"><span class="pre">%</span></tt> is not used: the <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#execute"><tt class="docutils literal"><span class="pre">.execute()</span></tt></a> function
+accepts the values tuple or dictionary as second parameter.</li>
+<li>The variables placeholder must always be a <tt class="docutils literal"><span class="pre">%s</span></tt>, even if a different
+placeholder (such as a <tt class="docutils literal"><span class="pre">%d</span></tt> for an integer) may look more appropriate.</li>
+<li>For positional variables binding, the second argument must always be a
+tuple, even if it contains a single variable.</li>
+<li>Only variable values should be bound via this method: it shouldn't be used
+to set table or field names. For these elements, ordinary string formatting
+should be used before running <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#execute"><tt class="docutils literal"><span class="pre">.execute()</span></tt></a>.</li>
+</ul>
+</blockquote>
+<div class="section" id="adapting-new-types">
+<h2><a name="adapting-new-types">Adapting new types</a></h2>
+<p>Any Python class or type can be adapted to an SQL string.  Adaptation mechanism
+is similar to the Object Adaptation proposed in the <a class="reference" href="http://www.python.org/peps/pep-0246.html">PEP-246</a> and is exposed
+by the <a class="reference" href="api/private/psycopg2._psycopg-module.html#adapt"><tt class="docutils literal"><span class="pre">adapt()</span></tt></a> function.</p>
+<p>psycopg2 <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#execute"><tt class="docutils literal"><span class="pre">.execute()</span></tt></a> method adapts its <tt class="docutils literal"><span class="pre">vars</span></tt> arguments to the <a class="reference" href="api/private/psycopg2._psycopg.ISQLQuote-class.html"><tt class="docutils literal"><span class="pre">ISQLQuote</span></tt></a>
+protocol.  Objects that conform to this protocol expose a <tt class="docutils literal"><span class="pre">getquoted()</span></tt> method
+returning the SQL representation of the object as a string.</p>
+<p>The easiest way to adapt an object to an SQL string is to register an adapter
+function via the <a class="reference" href="api/public/psycopg2.extensions-module.html#register_adapter"><tt class="docutils literal"><span class="pre">register_adapter()</span></tt></a> function.  The adapter function must take
+the value to be adapted as argument and return a conform object.  A convenient
+object is the <a class="reference" href="api/private/psycopg2._psycopg-module.html#AsIs"><tt class="docutils literal"><span class="pre">AsIs</span></tt></a> wrapper, whose <tt class="docutils literal"><span class="pre">getquoted()</span></tt> result is simply the
+<tt class="docutils literal"><span class="pre">str()</span></tt>ingification of the wrapped object.</p>
+<p>Example: mapping of a <tt class="docutils literal"><span class="pre">Point</span></tt> class into the <tt class="docutils literal"><span class="pre">point</span></tt> PostgreSQL geometric
+type:</p>
+<pre class="literal-block">
+from psycopg2.extensions import adapt, register_adapter, AsIs
+
+class Point(object):
+    def __init__(self, x=0.0, y=0.0):
+        self.x = x
+        self.y = y
+
+def adapt_point(point):
+    return AsIs(&quot;'(%s,%s)'&quot; % (adapt(point.x), adapt(point.y)))
+    
+register_adapter(Point, adapt_point)
+
+curs.execute(&quot;INSERT INTO atable (apoint) VALUES (%s)&quot;, 
+             (Point(1.23, 4.56),))
+</pre>
+<p>The above function call results in the SQL command:</p>
+<pre class="literal-block">
+INSERT INTO atable (apoint) VALUES ((1.23, 4.56));
+</pre>
+</div>
+</div>
+<div class="section" id="type-casting-of-sql-types-into-python-values">
+<h1><a name="type-casting-of-sql-types-into-python-values">Type casting of SQL types into Python values</a></h1>
+<p>PostgreSQL objects read from the database can be adapted to Python objects
+through an user-defined adapting function.  An adapter function takes two
+argments: the object string representation as returned by PostgreSQL and the
+cursor currently being read, and should return a new Python object.  For
+example, the following function parses a PostgreSQL <tt class="docutils literal"><span class="pre">point</span></tt> into the
+previously defined <tt class="docutils literal"><span class="pre">Point</span></tt> class:</p>
+<pre class="literal-block">
+def cast_point(value, curs):
+    if value is not None:
+            # Convert from (f1, f2) syntax using a regular expression.
+        m = re.match(&quot;\((.*),(.*)\)&quot;, value) 
+        if m:
+            return Point(float(m.group(1)), float(m.group(2)))
+</pre>
+<p>To create a mapping from the PostgreSQL type (either standard or user-defined),
+its <tt class="docutils literal"><span class="pre">oid</span></tt> must be known. It can be retrieved either by the second column of
+the cursor description:</p>
+<pre class="literal-block">
+curs.execute(&quot;SELECT NULL::point&quot;)
+point_oid = curs.description[0][1]   # usually returns 600
+</pre>
+<p>or by querying the system catalogs for the type name and namespace (the
+namespace for system objects is <tt class="docutils literal"><span class="pre">pg_catalog</span></tt>):</p>
+<pre class="literal-block">
+curs.execute(&quot;&quot;&quot;
+    SELECT pg_type.oid
+      FROM pg_type JOIN pg_namespace
+             ON typnamespace = pg_namespace.oid
+     WHERE typname = %(typename)s
+       AND nspname = %(namespace)s&quot;&quot;&quot;,
+            {'typename': 'point', 'namespace': 'pg_catalog'})
+    
+point_oid = curs.fetchone()[0]
+</pre>
+<p>After you know the object <tt class="docutils literal"><span class="pre">oid</span></tt>, you must can and register the new type:</p>
+<pre class="literal-block">
+POINT = psycopg2.extensions.new_type((point_oid,), &quot;POINT&quot;, cast_point)
+psycopg2.extensions.register_type(POINT)
+</pre>
+<p>The <a class="reference" href="api/private/psycopg2._psycopg-module.html#new_type"><tt class="docutils literal"><span class="pre">new_type()</span></tt></a> function binds the object oids (more than one can be
+specified) to the adapter function.  <a class="reference" href="api/private/psycopg2._psycopg-module.html#register_type"><tt class="docutils literal"><span class="pre">register_type()</span></tt></a> completes the spell.
+Conversion is automatically performed when a column whose type is a registered
+<tt class="docutils literal"><span class="pre">oid</span></tt> is read:</p>
+<pre class="literal-block">
+curs.execute(&quot;SELECT '(10.2,20.3)'::point&quot;)
+point = curs.fetchone()[0]
+print type(point), point.x, point.y
+# Prints: &quot;&lt;class '__main__.Point'&gt; 10.2 20.3&quot;
+</pre>
+</div>
+<div class="section" id="working-with-times-and-dates">
+<h1><a name="working-with-times-and-dates">Working with times and dates</a></h1>
+</div>
+<div class="section" id="receiving-notifys">
+<h1><a name="receiving-notifys">Receiving NOTIFYs</a></h1>
+</div>
+<div class="section" id="using-copy-to-and-copy-from">
+<h1><a name="using-copy-to-and-copy-from">Using COPY TO and COPY FROM</a></h1>
+<p>psycopg2 <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html"><tt class="docutils literal"><span class="pre">cursor</span></tt></a> object provides an interface to the efficient <a class="reference" href="http://www.postgresql.org/docs/8.1/static/sql-copy.html">PostgreSQL
+COPY command</a> to move data from files to tables and back.</p>
+<p>The <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#copy_to"><tt class="docutils literal"><span class="pre">.copy_to(file,</span> <span class="pre">table)</span></tt></a> method writes the content of the table
+named <tt class="docutils literal"><span class="pre">table</span></tt> <em>to</em> the file-like object <tt class="docutils literal"><span class="pre">file</span></tt>. <tt class="docutils literal"><span class="pre">file</span></tt> must have a
+<tt class="docutils literal"><span class="pre">write()</span></tt> method.</p>
+<p>The <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#copy_from"><tt class="docutils literal"><span class="pre">.copy_from(file,</span> <span class="pre">table)</span></tt></a> reads data <em>from</em> the file-like object
+<tt class="docutils literal"><span class="pre">file</span></tt> appending them to the table named <tt class="docutils literal"><span class="pre">table</span></tt>. <tt class="docutils literal"><span class="pre">file</span></tt> must have both
+<tt class="docutils literal"><span class="pre">read()</span></tt> and <tt class="docutils literal"><span class="pre">readline()</span></tt> method.</p>
+<p>Both methods accept two optional arguments: <tt class="docutils literal"><span class="pre">sep</span></tt> (defaulting to a tab) is
+the columns separator and <tt class="docutils literal"><span class="pre">null</span></tt> (defaulting to <tt class="docutils literal"><span class="pre">\N</span></tt>) represents <tt class="docutils literal"><span class="pre">NULL</span></tt>
+values in the file.</p>
+</div>
+<div class="section" id="postgresql-status-message-and-executed-query">
+<h1><a name="postgresql-status-message-and-executed-query">PostgreSQL status message and executed query</a></h1>
+<p><a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html"><tt class="docutils literal"><span class="pre">cursor</span></tt></a> objects have two special fields related to the last executed query:</p>
+<blockquote>
+<ul class="simple">
+<li><a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#query"><tt class="docutils literal"><span class="pre">.query</span></tt></a> is the textual representation (str or unicode, depending on what
+was passed to <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#execute"><tt class="docutils literal"><span class="pre">.execute()</span></tt></a> as first argument) of the query <em>after</em> argument
+binding and mogrification has been applied. To put it another way, <a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#query"><tt class="docutils literal"><span class="pre">.query</span></tt></a>
+is the <em>exact</em> query that was sent to the PostgreSQL backend.</li>
+<li><a class="reference" href="api/private/psycopg2._psycopg.cursor-class.html#statusmessage"><tt class="docutils literal"><span class="pre">.statusmessage</span></tt></a> is the status message that the backend sent upon query
+execution. It usually contains the basic type of the query (SELECT,
+INSERT, UPDATE, ...) and some additional information like the number of
+rows updated and so on. Refer to the PostgreSQL manual for more
+information.</li>
+</ul>
+</blockquote>
+</div>
+</div>
+</body>
+</html>
diff --git a/psycopg2/doc/extensions.rst b/psycopg2/doc/extensions.rst
new file mode 100644 (file)
index 0000000..3bdc680
--- /dev/null
@@ -0,0 +1,260 @@
+=======================================
+ psycopg 2 extensions to the DBAPI 2.0
+=======================================
+
+This document is a short summary of the extensions built in psycopg 2.0.x over
+the standard `Python Database API Specification 2.0`__, usually called simply
+DBAPI-2.0 or even PEP-249.  Before reading on this document please make sure
+you already know how to program in Python using a DBAPI-2.0 compliant driver:
+basic concepts like opening a connection, executing queries and commiting or
+rolling back a transaction will not be explained but just used.
+
+.. __: http://www.python.org/peps/pep-0249.html
+
+Many objects and extension functions are defined in the `psycopg2.extensions`
+module.
+
+
+Connection and cursor factories
+===============================
+
+psycopg 2 exposes two new-style classes that can be sub-classed and expanded to
+adapt them to the needs of the programmer: `cursor` and `connection`.  The
+`connection` class is usually sub-classed only to provide an easy way to create
+customized cursors but other uses are possible. `cursor` is much more
+interesting, because it is the class where query building, execution and result
+type-casting into Python variables happens.
+
+An example of cursor subclass performing logging is::
+
+    import psycopg2
+    import psycopg2.extensions
+    import logging
+
+    class LoggingCursor(psycopg2.extensions.cursor):
+        def execute(self, sql, args=None):
+            logger = logging.getLogger('sql_debug')
+            logger.info(self.mogrify(sql, args))
+
+            try:
+                psycopg2.extensions.cursor.execute(self, sql, args)
+            except Exception, exc:
+                logger.error("%s: %s" % (exc.__class__.__name__, exc))
+                raise
+
+    conn = psycopg2.connect(DSN)
+    curs = conn.cursor(cursor_factory=LoggingCursor)
+    curs.execute("INSERT INTO mytable VALUES (%s, %s, %s);",
+                 (10, 20, 30))
+
+
+Row factories
+-------------
+
+tzinfo factories
+----------------
+
+
+Setting transaction isolation levels
+====================================
+
+psycopg2 connection objects hold informations about the PostgreSQL `transaction
+isolation level`_.  The current transaction level can be read from the
+`.isolation_level` attribute.  The default isolation level is ``READ
+COMMITTED``.  A different isolation level con be set through the
+`.set_isolation_level()` method.  The level can be set to one of the following
+constants, defined in `psycopg2.extensions`:
+
+`ISOLATION_LEVEL_AUTOCOMMIT`
+    No transaction is started when command are issued and no
+    `.commit()`/`.rollback()` is required.  Some PostgreSQL command such as
+    ``CREATE DATABASE`` can't run into a transaction: to run such command use
+    `.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)`.
+    
+`ISOLATION_LEVEL_READ_COMMITTED`
+    This is the default value.  A new transaction is started at the first
+    `.execute()` command on a cursor and at each new `.execute()` after a
+    `.commit()` or a `.rollback()`.  The transaction runs in the PostgreSQL
+    ``READ COMMITTED`` isolation level.
+    
+`ISOLATION_LEVEL_SERIALIZABLE`
+    Transactions are run at a ``SERIALIZABLE`` isolation level.
+
+
+.. _transaction isolation level: 
+   http://www.postgresql.org/docs/8.1/static/transaction-iso.html
+
+
+Adaptation of Python values to SQL types
+========================================
+
+psycopg2 casts Python variables to SQL literals by type.  Standard Python types
+are already adapted to the proper SQL literal.
+
+Example: the Python function::
+
+    curs.execute("""INSERT INTO atable (anint, adate, astring)
+                     VALUES (%s, %s, %s)""",
+                 (10, datetime.date(2005, 11, 18), "O'Reilly"))
+
+is converted into the SQL command::
+
+    INSERT INTO atable (anint, adate, astring)
+     VALUES (10, '2005-11-18', 'O''Reilly');
+
+Named arguments are supported too with ``%(name)s`` placeholders. Notice that:
+
+  - The Python string operator ``%`` is not used: the `.execute()` function
+    accepts the values tuple or dictionary as second parameter.
+
+  - The variables placeholder must always be a ``%s``, even if a different
+    placeholder (such as a ``%d`` for an integer) may look more appropriate.
+
+  - For positional variables binding, the second argument must always be a
+    tuple, even if it contains a single variable.
+
+  - Only variable values should be bound via this method: it shouldn't be used
+    to set table or field names. For these elements, ordinary string formatting
+    should be used before running `.execute()`.
+
+
+Adapting new types
+------------------
+
+Any Python class or type can be adapted to an SQL string.  Adaptation mechanism
+is similar to the Object Adaptation proposed in the `PEP-246`_ and is exposed
+by the `adapt()` function.
+
+psycopg2 `.execute()` method adapts its ``vars`` arguments to the `ISQLQuote`
+protocol.  Objects that conform to this protocol expose a ``getquoted()`` method
+returning the SQL representation of the object as a string.
+
+The easiest way to adapt an object to an SQL string is to register an adapter
+function via the `register_adapter()` function.  The adapter function must take
+the value to be adapted as argument and return a conform object.  A convenient
+object is the `AsIs` wrapper, whose ``getquoted()`` result is simply the
+``str()``\ ingification of the wrapped object.
+
+Example: mapping of a ``Point`` class into the ``point`` PostgreSQL geometric
+type::
+
+    from psycopg2.extensions import adapt, register_adapter, AsIs
+    
+    class Point(object):
+        def __init__(self, x=0.0, y=0.0):
+            self.x = x
+            self.y = y
+    
+    def adapt_point(point):
+        return AsIs("'(%s,%s)'" % (adapt(point.x), adapt(point.y)))
+        
+    register_adapter(Point, adapt_point)
+    
+    curs.execute("INSERT INTO atable (apoint) VALUES (%s)", 
+                 (Point(1.23, 4.56),))
+
+The above function call results in the SQL command::
+
+    INSERT INTO atable (apoint) VALUES ((1.23, 4.56));
+
+
+.. _PEP-246: http://www.python.org/peps/pep-0246.html
+
+
+Type casting of SQL types into Python values
+============================================
+
+PostgreSQL objects read from the database can be adapted to Python objects
+through an user-defined adapting function.  An adapter function takes two
+argments: the object string representation as returned by PostgreSQL and the
+cursor currently being read, and should return a new Python object.  For
+example, the following function parses a PostgreSQL ``point`` into the
+previously defined ``Point`` class::
+
+    def cast_point(value, curs):
+        if value is not None:
+               # Convert from (f1, f2) syntax using a regular expression.
+            m = re.match("\((.*),(.*)\)", value) 
+            if m:
+                return Point(float(m.group(1)), float(m.group(2)))
+                
+To create a mapping from the PostgreSQL type (either standard or user-defined),
+its ``oid`` must be known. It can be retrieved either by the second column of
+the cursor description::
+
+    curs.execute("SELECT NULL::point")
+    point_oid = curs.description[0][1]   # usually returns 600
+
+or by querying the system catalogs for the type name and namespace (the
+namespace for system objects is ``pg_catalog``)::
+
+    curs.execute("""
+        SELECT pg_type.oid
+          FROM pg_type JOIN pg_namespace
+                 ON typnamespace = pg_namespace.oid
+         WHERE typname = %(typename)s
+           AND nspname = %(namespace)s""",
+                {'typename': 'point', 'namespace': 'pg_catalog'})
+        
+    point_oid = curs.fetchone()[0]
+
+After you know the object ``oid``, you must can and register the new type::
+
+    POINT = psycopg2.extensions.new_type((point_oid,), "POINT", cast_point)
+    psycopg2.extensions.register_type(POINT)
+
+The `new_type()` function binds the object oids (more than one can be
+specified) to the adapter function.  `register_type()` completes the spell.
+Conversion is automatically performed when a column whose type is a registered
+``oid`` is read::
+
+    curs.execute("SELECT '(10.2,20.3)'::point")
+    point = curs.fetchone()[0]
+    print type(point), point.x, point.y
+    # Prints: "<class '__main__.Point'> 10.2 20.3"
+
+
+Working with times and dates
+============================
+
+
+Receiving NOTIFYs
+=================
+
+
+Using COPY TO and COPY FROM
+===========================
+
+psycopg2 `cursor` object provides an interface to the efficient `PostgreSQL
+COPY command`__ to move data from files to tables and back.
+
+The `.copy_to(file, table)` method writes the content of the table
+named ``table`` *to* the file-like object ``file``. ``file`` must have a
+``write()`` method.
+
+The `.copy_from(file, table)` reads data *from* the file-like object
+``file`` appending them to the table named ``table``. ``file`` must have both
+``read()`` and ``readline()`` method.
+
+Both methods accept two optional arguments: ``sep`` (defaulting to a tab) is
+the columns separator and ``null`` (defaulting to ``\N``) represents ``NULL``
+values in the file.
+
+.. __: http://www.postgresql.org/docs/8.1/static/sql-copy.html
+
+
+PostgreSQL status message and executed query
+============================================
+
+`cursor` objects have two special fields related to the last executed query:
+
+  - `.query` is the textual representation (str or unicode, depending on what
+    was passed to `.execute()` as first argument) of the query *after* argument
+    binding and mogrification has been applied. To put it another way, `.query`
+    is the *exact* query that was sent to the PostgreSQL backend.
+    
+  - `.statusmessage` is the status message that the backend sent upon query
+    execution. It usually contains the basic type of the query (SELECT,
+    INSERT, UPDATE, ...) and some additional information like the number of
+    rows updated and so on. Refer to the PostgreSQL manual for more
+    information.
diff --git a/pycurl/COPYING b/pycurl/COPYING
new file mode 100644 (file)
index 0000000..99dce33
--- /dev/null
@@ -0,0 +1,504 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/pycurl/ChangeLog b/pycurl/ChangeLog
new file mode 100644 (file)
index 0000000..0e5ae7e
--- /dev/null
@@ -0,0 +1,759 @@
+Version 7.13.1 [requires libcurl-7.13.1 or better]
+--------------
+
+2005-03-04  Kjetil Jacobsen  <kjetilja>
+
+        * Use METH_NOARGS where appropriate.
+
+2005-03-03  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for CURLFORM API with HTTPPOST: Supports a
+          a tuple with pairs of options and values instead of just
+          supporting string contents.  See tests/test_post2.py
+          for example usage.  Options are FORM_CONTENTS, FORM_FILE and
+          FORM_CONTENTTYPE, corresponding to the CURLFORM_* options,
+          and values are strings.
+
+2005-02-13  Markus F.X.J. Oberhumer <mfx>
+
+        * Read callbacks (pycurl.READFUNCTION) can now return
+          pycurl.READFUNC_ABORT to immediately abort the current transfer.
+
+        * The INFILESIZE, MAXFILESIZE, POSTFIELDSIZE and RESUME_FROM
+          options now automatically use the largefile version to handle
+          files > 2GB.
+
+        * Added missing pycurl.PORT constant.
+
+
+Version 7.13.0
+--------------
+
+2005-02-10  Kjetil Jacobsen  <kjetilja>
+
+        * Added file_upload.py to examples, shows how to upload
+          a file.
+
+        * Added CURLOPT_IOCTLFUNCTION/DATA.
+
+        * Added options from libcurl 7.13.0: FTP_ACCOUNT, SOURCE_URL,
+          SOURCE_QUOTE.
+
+        * Obsoleted options: SOURCE_HOST, SOURCE_PATH, SOURCE_PORT,
+          PASV_HOST.
+
+
+Version 7.12.3
+--------------
+
+2004-12-22  Markus F.X.J. Oberhumer <mfx>
+
+        * Added CURLINFO_NUM_CONNECTS and CURLINFO_SSL_ENGINES.
+
+        * Added some other missing constants.
+
+        * Updated pycurl.version_info() to return a 12-tuple
+          instead of a 9-tuple.
+
+
+Version 7.12.2
+--------------
+
+2004-10-15  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_FTPSSLAUTH (and CURLFTPAUTH_*).
+
+        * Added CURLINFO_OS_ERRNO.
+
+2004-08-17 Kjetil Jacobsen <kjetilja>
+
+        * Use LONG_LONG instead of PY_LONG_LONG to make pycurl compile
+          on Python versions < 2.3 (fix from Domenico Andreoli
+          <cavok at libero.it>).
+
+
+Version 7.12.1
+--------------
+
+2004-08-02  Kjetil Jacobsen  <kjetilja>
+
+        * Added INFOTYPE_SSL_DATA_IN/OUT.
+
+2004-07-16  Markus F.X.J. Oberhumer <mfx>
+
+        * WARNING: removed deprecated PROXY_, TIMECOND_ and non-prefixed
+          INFOTYPE constant names. See ChangeLog entry 2003-06-10.
+
+2004-06-21  Kjetil Jacobsen  <kjetilja>
+
+        * Added test program for HTTP post using the read callback (see
+          tests/test_post3.py for details).
+
+        * Use the new CURL_READFUNC_ABORT return code where appropriate
+          to avoid hanging in perform() when read callbacks are used.
+
+        * Added support for libcurl 7.12.1 CURLOPT features:
+          SOURCE_HOST, SOURCE_USERPWD, SOURCE_PATH, SOURCE_PORT,
+          PASV_HOST, SOURCE_PREQUOTE, SOURCE_POSTQUOTE.
+
+2004-06-08  Markus F.X.J. Oberhumer <mfx>
+
+        * Setting CURLOPT_POSTFIELDS now allows binary data and
+          automatically sets CURLOPT_POSTFIELDSIZE for you. If you really
+          want a different size you have to manually set POSTFIELDSIZE
+          after setting POSTFIELDS.
+          (Based on a patch by Martin Muenstermann).
+
+2004-06-05  Markus F.X.J. Oberhumer <mfx>
+
+        * Added stricter checks within the callback handlers.
+
+        * Unify the behaviour of int and long parameters where appropriate.
+
+
+Version 7.12
+------------
+
+2004-05-18  Kjetil Jacobsen  <kjetilja>
+
+        * WARNING: To simplify code maintenance pycurl now requires
+          libcurl 7.11.2 and Python 2.2 or newer to work.
+
+        * GC support is now always enabled.
+
+
+Version 7.11.3
+--------------
+
+2004-04-30  Kjetil Jacobsen  <kjetilja>
+
+        * Do not use the deprecated curl_formparse function.
+          API CHANGE: HTTPPOST now takes a list of tuples where each
+          tuple contains a form name and a form value, both strings
+          (see test/test_post2.py for example usage).
+
+        * Found a possible reference count bug in the multithreading
+          code which may have contributed to the long-standing GC
+          segfault which has haunted pycurl.  Fingers crossed.
+
+
+Version 7.11.2
+--------------
+
+2004-04-21  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for libcurl 7.11.2 CURLOPT features:
+          CURLOPT_TCP_NODELAY.
+
+2004-03-25 Kjetil Jacobsen   <kjetilja>
+
+        * Store Python longs in off_t with PyLong_AsLongLong instead
+          of PyLong_AsLong.  Should make the options which deal
+          with large files behave a little better.  Note that this
+          requires the long long support in Python 2.2 or newer to
+          work properly.
+
+
+Version 7.11.1
+--------------
+
+2004-03-16  Kjetil Jacobsen  <kjetilja>
+
+        * WARNING: Removed support for the PASSWDFUNCTION callback, which
+          is no longer supported by libcurl.
+
+2004-03-15  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for libcurl 7.11.1 CURLOPT features:
+          CURLOPT_POSTFIELDSIZE_LARGE.
+
+
+Version 7.11.0
+--------------
+
+2004-02-11  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for libcurl 7.11.0 CURLOPT features:
+          INFILESIZE_LARGE, RESUME_FROM_LARGE, MAXFILESIZE_LARGE
+          and FTP_SSL.
+
+        * Circular garbage collection support can now be enabled or
+          disabled by passing the '--use-gc=[1|0]' parameter to setup.py
+          when building pycurl.
+
+        * HTTP_VERSION options are known as CURL_HTTP_VERSION_NONE,
+          CURL_HTTP_VERSION_1_0, CURL_HTTP_VERSION_1_1 and
+          CURL_HTTP_VERSION_LAST.
+
+2003-11-16  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for these new libcurl 7.11.0 features:
+          CURLOPT_NETRC_FILE.
+
+
+Version 7.10.8
+--------------
+
+2003-11-04  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for these new libcurl 7.10.8 features:
+          CURLOPT_FTP_RESPONSE_TIMEOUT, CURLOPT_IPRESOLVE,
+          CURLOPT_MAXFILESIZE,
+          CURLINFO_HTTPAUTH_AVAIL, CURLINFO_PROXYAUTH_AVAIL,
+          CURL_IPRESOLVE_* constants.
+
+        * Added support for these new libcurl 7.10.7 features:
+          CURLOPT_FTP_CREATE_MISSING_DIRS, CURLOPT_PROXYAUTH,
+          CURLINFO_HTTP_CONNECTCODE.
+
+
+2003-10-28  Kjetil Jacobsen  <kjetilja>
+
+        * Added missing CURLOPT_ENCODING option (patch by Martijn
+          Boerwinkel <xim@xs4all.nl>)
+
+
+Version 7.10.6
+--------------
+
+2003-07-29  Markus F.X.J. Oberhumer <mfx>
+
+        * Started working on support for CURLOPT_SSL_CTX_FUNCTION and
+          CURLOPT_SSL_CTX_DATA (libcurl-7.10.6) - not yet finished.
+
+2003-06-10  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for CURLOPT_HTTPAUTH (libcurl-7.10.6), including
+          the new HTTPAUTH_BASIC, HTTPAUTH_DIGEST, HTTPAUTH_GSSNEGOTIATE
+          and HTTPAUTH_NTML constants.
+
+        * Some constants were renamed for consistency:
+
+          All curl_infotype constants are now prefixed with "INFOTYPE_",
+          all curl_proxytype constants are prefixed with "PROXYTYPE_" instead
+          of "PROXY_", and all curl_TimeCond constants are now prefixed
+          with "TIMECONDITION_" instead of "TIMECOND_".
+
+          (The old names are still available but will get removed
+          in a future release.)
+
+        * WARNING: Removed the deprecated pycurl.init() and pycurl.multi_init()
+          names - use pycurl.Curl() and pycurl.CurlMulti() instead.
+
+        * WARNING: Removed the deprecated Curl.cleanup() and
+          CurlMulti.cleanup() methods - use Curl.close() and
+          CurlMulti.close() instead.
+
+
+Version 7.10.5
+--------------
+
+2003-05-15  Markus F.X.J. Oberhumer <mfx>
+
+        * Added support for CURLOPT_FTP_USE_EPRT (libcurl-7.10.5).
+
+        * Documentation updates.
+
+2003-05-07  Eric S. Raymond  <esr@snark.thyrsus.com>
+
+        * Lifted all HTML docs to clean XHTML, verified by tidy.
+
+2003-05-02  Markus F.X.J. Oberhumer <mfx>
+
+        * Fixed some `int' vs. `long' mismatches that affected 64-bit systems.
+
+        * Fixed wrong pycurl.CAPATH constant.
+
+2003-05-01  Markus F.X.J. Oberhumer <mfx>
+
+        * Added new method Curl.errstr() which returns the internal
+        libcurl error buffer string of the handle.
+
+
+Version 7.10.4.2
+----------------
+
+2003-04-15  Markus F.X.J. Oberhumer <mfx>
+
+        * Allow compilation against the libcurl-7.10.3 release - some
+        recent Linux distributions (e.g. Mandrake 9.1) ship with 7.10.3,
+        and apart from the new CURLOPT_UNRESTRICTED_AUTH option there is
+        no need that we require libcurl-7.10.4.
+
+
+Version 7.10.4
+--------------
+
+2003-04-01  Kjetil Jacobsen  <kjetilja>
+
+        * Markus added CURLOPT_UNRESTRICTED_AUTH (libcurl-7.10.4).
+
+2003-02-25  Kjetil Jacobsen  <kjetilja>
+
+        * Fixed some broken test code and removed the fileupload test
+        since it didn't work properly.
+
+2003-01-28  Kjetil Jacobsen  <kjetilja>
+
+        * Some documentation updates by Markus and me.
+
+2003-01-22  Kjetil Jacobsen  <kjetilja>
+
+        * API CHANGE: the CurlMulti.info_read() method now returns
+        a separate array with handles that failed.  Each entry in this array
+        is a tuple with (curl object, error number, error message).
+        This addition makes it simpler to do error checking of individual
+        curl objects when using the multi interface.
+
+
+Version 7.10.3
+--------------
+
+2003-01-13  Kjetil Jacobsen  <kjetilja>
+
+        * PycURL memory usage has been reduced.
+
+2003-01-10  Kjetil Jacobsen  <kjetilja>
+
+        * Added 'examples/retriever-multi.py' which shows how to retrieve
+        a set of URLs concurrently using the multi interface.
+
+2003-01-09  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for CURLOPT_HTTP200ALIASES.
+
+2002-11-22  Kjetil Jacobsen  <kjetilja>
+
+        * Updated pycurl documentation in the 'doc' directory.
+
+2002-11-21  Kjetil Jacobsen  <kjetilja>
+
+        * Updated and improved 'examples/curl.py'.
+
+        * Added 'tests/test_multi6.py' which shows how to use the
+        info_read method with CurlMulti.
+
+2002-11-19  Kjetil Jacobsen  <kjetilja>
+
+        * Added new method CurlMulti.info_read().
+
+
+Version 7.10.2
+--------------
+
+2002-11-14  Kjetil Jacobsen <kjetilja>
+
+        * Free options set with setopt after cleanup is called, as cleanup
+        assumes that options are still valid when invoked.  This fixes the
+        bug with COOKIEJAR reported by Bastiaan Naber
+        <bastiaan@ricardis.tudelft.nl>.
+
+2002-11-06  Markus F.X.J. Oberhumer <mfx>
+
+        * Install documentation under /usr/share/doc instead of /usr/doc.
+        Also, start shipping the (unfinished) HTML docs and some
+        basic test scripts.
+
+2002-10-30  Markus F.X.J. Oberhumer <mfx>
+
+        * API CHANGE: For integral values, Curl.getinfo() now returns a
+        Python-int instead of a Python-long.
+
+
+Version 7.10.1
+--------------
+
+2002-10-03  Markus F.X.J. Oberhumer <mfx>
+
+        * Added new module-level function version_info() from
+        libcurl-7.10.
+
+
+Version 7.10
+------------
+
+2002-09-13  Kjetil Jacobsen  <kjetilja>
+
+        * Added commandline options to setup.py for specifying the path to
+        'curl-config' (non-windows) and the curl installation directory
+        (windows).  See the 'INSTALL' file for details.
+
+        * Added CURLOPT_ENCODING, CURLOPT_NOSIGNAL and CURLOPT_BUFFERSIZE
+        from libcurl-7.10 (by Markus Oberhumer).
+
+
+Version 7.9.8.4
+---------------
+
+2002-08-28  Kjetil Jacobsen  <kjetilja>
+
+        * Added a simple web-browser example based on gtkhtml and pycurl.
+        See the file 'examples/gtkhtml_demo.py' for details.  The example
+        requires a working installation of gnome-python with gtkhtml
+        bindings enabled (pass --with-gtkhtml to gnome-python configure).
+
+2002-08-14  Kjetil Jacobsen  <kjetilja>
+
+        * Added new method 'select' on CurlMulti objects.  Example usage
+        in 'tests/test_multi5.py'.  This method is just an optimization of
+        the combined use of fdset and select.
+
+2002-08-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for curl_multi_fdset.  See the file
+        'tests/test_multi4.py' for example usage.  Contributed by Conrad
+        Steenberg <conrad@hep.caltech.edu>.
+
+        * perform() on multi objects now returns a tuple (result, number
+        of handles) like the libcurl interface does.
+
+2002-08-08  Kjetil Jacobsen  <kjetilja>
+
+        * Added the 'sfquery' script which retrieves a SourceForge XML
+        export object for a given project.  See the file 'examples/sfquery.py'
+        for details and usage.  'sfquery' was contributed by Eric
+        S. Raymond <esr@thyrsus.com>.
+
+2002-07-20  Markus F.X.J. Oberhumer <mfx>
+
+        * API enhancements: added Curl() and CurlMulti() as aliases for
+        init() and multi_init(), and added close() methods as aliases
+        for the cleanup() methods. The new names much better match
+        the actual intended use of the objects, and they also nicely
+        correspond to Python's file object.
+
+        * Also, all constants for Curl.setopt() and Curl.getinfo() are now
+        visible from within Curl objects.
+
+        All changes are fully backward-compatible.
+
+
+Version 7.9.8.3
+---------------
+
+2002-07-16  Markus F.X.J. Oberhumer <mfx>
+
+        * Under Python 2.2 or better, Curl and CurlMulti objects now
+        automatically participate in cyclic garbarge collection
+        (using the gc module).
+
+
+Version 7.9.8.2
+---------------
+
+2002-07-05  Markus F.X.J. Oberhumer <mfx>
+
+        * Curl and CurlMulti objects now support standard Python attributes.
+        See tests/test_multi2.py for an example.
+
+2002-07-02  Kjetil Jacobsen  <kjetilja>
+
+        * Added support for the multi-interface.
+
+
+Version 7.9.8.1
+---------------
+
+2002-06-25  Markus F.X.J. Oberhumer <mfx>
+
+        * Fixed a couple of `int' vs. `size_t' mismatches in callbacks
+        and Py_BuildValue() calls.
+
+2002-06-25  Kjetil Jacobsen  <kjetilja>
+
+        * Use 'double' type instead of 'size_t' for progress callbacks
+        (by Conrad Steenberg <conrad@hep.caltech.edu>).  Also cleaned up
+        some other type mismatches in the callback interfaces.
+
+2002-06-24  Kjetil Jacobsen  <kjetilja>
+
+        * Added example code on how to upload a file using HTTPPOST in
+        pycurl (code by Amit Mongia <amit_mongia@hotmail.com>).  See the
+        file 'test_fileupload.py' for details.
+
+
+Version 7.9.8
+-------------
+
+2002-06-24  Kjetil Jacobsen  <kjetilja>
+
+        * Resolved some build problems on Windows (by Markus Oberhumer).
+
+2002-06-19  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_CAPATH.
+
+        * Added option constants for CURLOPT_NETRC: CURL_NETRC_OPTIONAL,
+        CURL_NETRC_IGNORED and CURL_NETRC_REQUIRED.
+
+        * Added option constants for CURLOPT_TIMECONDITION:
+        TIMECOND_IFMODSINCE and TIMECOND_IFUNMODSINCE.
+
+        * Added an simple example crawler, which downloads documents
+        listed in a file with a configurable number of worker threads.
+        See the file 'crawler.py' in the 'tests' directory for details.
+
+        * Removed the redundant 'test_xmlrpc2.py' test script.
+
+        * Disallow recursive callback invocations (by Markus Oberhumer).
+
+2002-06-18  Kjetil Jacobsen  <kjetilja>
+
+        * Made some changes to setup.py which should fix the build
+        problems on RedHat 7.3 (suggested by Benji <benji@kioza.net>).
+
+        * Use CURLOPT_READDATA instead of CURLOPT_INFILE, and
+        CURLOPT_WRITEDATA instead of CURLOPT_FILE.  Also fixed some
+        reference counting bugs with file objects.
+
+        * CURLOPT_FILETIME and CURLINFO_FILETIME had a namespace clash
+        which caused them not to work.  Use OPT_FILETIME for setopt() and
+        INFO_FILETIME for getinfo().  See example usage in
+        'test_getinfo.py' for details.
+
+
+Version 7.9.7
+-------------
+
+2002-05-20  Kjetil Jacobsen  <kjetilja>
+
+        * New versioning scheme.  Pycurl now has the same version number
+        as the libcurl version it was built with.  The pycurl version
+        number thus indicates which version of libcurl is required to run.
+
+2002-05-17  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLINFO_REDIRECT_TIME and CURLINFO_REDIRECT_COUNT.
+
+2002-04-27  Kjetil Jacobsen  <kjetilja>
+
+        * Fixed potential memory leak and thread race (by Markus
+        Oberhumer).
+
+
+Version 0.4.9
+-------------
+
+2002-04-15  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_DEBUGFUNCTION to allow debug callbacks to be
+        specified (see the file 'test_debug.py' for details on how to use
+        debug callbacks).
+
+        * Added CURLOPT_DNS_USE_GLOBAL_CACHE and
+        CURLOPT_DNS_CACHE_TIMEOUT.
+
+        * Fixed a segfault when finalizing curl objects in Python 1.5.2.
+
+        * Now requires libcurl 7.9.6 or greater.
+
+2002-04-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added 'test_post2.py' file which is another example on how to
+        issue POST requests.
+
+2002-04-11  Markus F.X.J. Oberhumer <mfx>
+
+        * Added the 'test_post.py' file which demonstrates the use of
+        POST requests.
+
+
+Version 0.4.8
+-------------
+
+2002-03-07  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLOPT_PREQUOTE.
+
+        * Now requires libcurl 7.9.5 or greater.
+
+        * Other minor code cleanups and bugfixes.
+
+2002-03-05  Kjetil Jacobsen  <kjetilja>
+
+        * Do not allow WRITEFUNCTION and WRITEHEADER on the same handle.
+
+
+Version 0.4.7
+-------------
+
+2002-02-27  Kjetil Jacobsen  <kjetilja>
+
+        * Abort callback if the thread state of the calling thread cannot
+        be determined.
+
+        * Check that the installed version of libcurl matches the
+        requirements of pycurl.
+
+2002-02-26  Kjetil Jacobsen  <kjetilja>
+
+        * Clarence Garnder <clarence@silcom.com> found a bug where string
+        arguments to setopt sometimes were prematurely deallocated, this
+        should now be fixed.
+
+2002-02-21  Kjetil Jacobsen  <kjetilja>
+
+        * Added the 'xmlrpc_curl.py' file which implements a transport
+        for xmlrpclib (xmlrpclib is part of Python 2.2).
+
+        * Added CURLINFO_CONTENT_TYPE.
+
+        * Added CURLOPT_SSLCERTTYPE, CURLOPT_SSLKEY, CURLOPT_SSLKEYTYPE,
+        CURLOPT_SSLKEYPASSWD, CURLOPT_SSLENGINE and
+        CURLOPT_SSLENGINE_DEFAULT.
+
+        * When thrown, the pycurl.error exception is now a tuple consisting
+        of the curl error code and the error message.
+
+        * Now requires libcurl 7.9.4 or greater.
+
+2002-02-19  Kjetil Jacobsen  <kjetilja>
+
+        * Fixed docstring for getopt() function.
+
+2001-12-18  Kjetil Jacobsen  <kjetilja>
+
+        * Updated the INSTALL information for Win32.
+
+2001-12-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added missing link flag to make pycurl build on MacOS X (by Matt
+        King <matt@gnik.com>).
+
+2001-12-06  Kjetil Jacobsen  <kjetilja>
+
+        * Added CURLINFO_STARTTRANSFER_TIME and CURLOPT_FTP_USE_EPSV from
+        libcurl 7.9.2.
+
+2001-12-01  Markus F.X.J. Oberhumer <mfx>
+
+        * Added the 'test_stringio.py' file which demonstrates the use of
+        StringIO objects as callback.
+
+2001-12-01  Markus F.X.J. Oberhumer <mfx>
+
+        * setup.py: Do not remove entries from a list while iterating
+        over it.
+
+2001-11-29  Kjetil Jacobsen  <kjetilja>
+
+        * Added code in setup.py to install on Windows.  Requires some
+        manual configuration (by Tino Lange <Tino.Lange@gmx.de>).
+
+2001-11-27  Kjetil Jacobsen  <kjetilja>
+
+        * Improved detection of where libcurl is installed in setup.py.
+        Should make it easier to install pycurl when libcurl is not
+        located in regular lib/include paths.
+
+2001-11-05  Kjetil Jacobsen  <kjetilja>
+
+        * Some of the newer options to setopt were missing, this should
+        now be fixed.
+
+2001-11-04  Kjetil Jacobsen  <kjetilja>
+
+        * Exception handling has been improved and should no longer throw
+        spurious exceptions (by Markus F.X.J. Oberhumer
+        <markus@oberhumer.com>).
+
+2001-10-15  Kjetil Jacobsen  <kjetilja>
+
+        * Refactored the test_gtk.py script to avoid global variables.
+
+2001-10-12  Kjetil Jacobsen  <kjetilja>
+
+        * Added module docstrings, terse perhaps, but better than nothing.
+
+        * Added the 'basicfirst.py' file which is a Python version of the
+        corresponding Perl script by Daniel.
+
+        * PycURL now works properly under Python 1.5 and 1.6 (by Markus
+        F.X.J. Oberhumer <markus@oberhumer.com>).
+
+        * Allow C-functions and Python methods as callbacks (by Markus
+        F.X.J. Oberhumer <markus@oberhumer.com>).
+
+        * Allow None as success result of write, header and progress
+        callback invocations (by Markus F.X.J. Oberhumer
+        <markus@oberhumer.com>).
+
+        * Added the 'basicfirst2.py' file which demonstrates the use of a
+        class method as callback instead of just a function.
+
+2001-08-21  Kjetil Jacobsen  <kjetilja>
+
+        * Cleaned up the script with GNOME/PycURL integration.
+
+2001-08-20  Kjetil Jacobsen  <kjetilja>
+
+        * Added another test script for shipping XML-RPC requests which
+        uses py-xmlrpc to encode the arguments (tests/test_xmlrpc2.py).
+
+2001-08-20  Kjetil Jacobsen  <kjetilja>
+
+        * Added test script for using PycURL and GNOME (tests/test_gtk.py).
+
+2001-08-20  Kjetil Jacobsen  <kjetilja>
+
+        * Added test script for using XML-RPC (tests/test_xmlrpc.py).
+
+        * Added more comments to the test sources.
+
+2001-08-06  Kjetil Jacobsen  <kjetilja>
+
+        * Renamed module namespace to pycurl instead of curl.
+
+2001-08-06  Kjetil Jacobsen  <kjetilja>
+
+        * Set CURLOPT_VERBOSE to 0 by default.
+
+2001-06-29  Kjetil Jacobsen  <kjetilja>
+
+        * Updated INSTALL, curl version 7.8 or greater is now mandatory to
+        use pycurl.
+
+2001-06-13  Kjetil Jacobsen  <kjetilja>
+
+        * Set NOPROGRESS to 1 by default.
+
+2001-06-07  Kjetil Jacobsen  <kjetilja>
+
+        * Added global_init/cleanup.
+
+2001-06-06  Kjetil Jacobsen  <kjetilja>
+
+        * Added HEADER/PROGRESSFUNCTION callbacks (see files in tests/).
+
+        * Added PASSWDFUNCTION callback (untested).
+
+        * Added READFUNCTION callback (untested).
+
+2001-06-05  Kjetil Jacobsen  <kjetilja>
+
+        * WRITEFUNCTION callbacks now work (see tests/test_cb.py for details).
+
+        * Preliminary distutils installation.
+
+        * Added CLOSEPOLICY constants to module namespace.
+
+2001-06-04  Kjetil Jacobsen  <kjetilja>
+
+        * Return -1 on error from Python callback in WRITEFUNCTION callback.
+
+2001-06-01  Kjetil Jacobsen  <kjetilja>
+
+        * Moved source to src and tests to tests directory.
+
+2001-05-31  Kjetil Jacobsen  <kjetilja>
+
+        * Added better type checking for setopt.
+
+2001-05-30  Kjetil Jacobsen  <kjetilja>
+
+        * Moved code to sourceforge.
+
+        * Added getinfo support.
+
+
+# vi:ts=8:et
diff --git a/pycurl/INSTALL b/pycurl/INSTALL
new file mode 100644 (file)
index 0000000..ef42cf6
--- /dev/null
@@ -0,0 +1,44 @@
+NOTE: You need Python and libcurl installed on your system to use or
+build pycurl.  Some RPM distributions of curl/libcurl do not include
+everything necessary to build pycurl, in which case you need to
+install the developer specific RPM which is usually called curl-dev.
+
+
+Distutils
+---------
+
+Assuming that distutils is installed (which it is by default on Python
+versions greater than 1.5.2) build and install pycurl with the
+following commands:
+
+    (if necessary, become root)
+    tar -zxvf pycurl-$VER.tar.gz
+    cd pycurl-$VER
+    python setup.py install
+
+$VER should be substituted with the version number, e.g. 7.10.5.
+
+Note that the installation script assumes that 'curl-config' can be
+located in your path setting.  If curl-config is installed outside
+your path or you want to force installation to use a particular
+version of curl-config, use the '--curl-config' commandline option to
+specify the location of curl-config.  Example:
+
+    python setup.py install --curl-config=/usr/local/bin/curl-config
+
+If libcurl is linked dynamically with pycurl, you may have to alter the
+LD_LIBRARY_PATH environment variable accordingly.  This normally
+applies only if there is more than one version of libcurl installed,
+e.g. one in /usr/lib and one in /usr/local/lib.
+
+
+Windows
+-------
+
+When installing on Windows, you need to manually configure the path to
+the curl source tree, specified with the CURL_DIR variable in the file
+'setup.py'.  The CURL_DIR variable can also be set using the
+commandline option '--curl-dir' when invoking setup.py:
+
+    python setup.py install --curl-dir=c:\curl-7.10.5
+
diff --git a/pycurl/MANIFEST.in b/pycurl/MANIFEST.in
new file mode 100644 (file)
index 0000000..f4e3837
--- /dev/null
@@ -0,0 +1,22 @@
+#
+# MANIFEST.in
+# Manifest template for creating the source distribution.
+#
+
+include ChangeLog
+include COPYING
+include INSTALL
+include Makefile
+include README
+include TODO
+include MANIFEST.in
+include src/Makefile
+include src/pycurl.c
+include python/curl/*.py
+include examples/*.py
+include tests/*.py
+include doc/*.html
+include setup_win32_ssl.py
+
+# exclude unfinished test scripts
+#exclude tests/test_multi_vs_thread.py
diff --git a/pycurl/Makefile b/pycurl/Makefile
new file mode 100644 (file)
index 0000000..9b2369d
--- /dev/null
@@ -0,0 +1,60 @@
+#
+# to use a specific python version call
+#   `make PYTHON=python2.2'
+#
+
+SHELL = /bin/sh
+
+PYTHON = python2.3
+PYTHON = python
+
+all build:
+       $(PYTHON) setup.py build
+
+build-7.10.8:
+       $(PYTHON) setup.py build --curl-config=/home/hosts/localhost/packages/curl-7.10.8/bin/curl-config
+
+test: build
+       $(PYTHON) tests/test_internals.py -q
+
+# (needs GNU binutils)
+strip: build
+       strip -p --strip-unneeded build/lib*/*.so
+       chmod -x build/lib*/*.so
+
+install install_lib:
+       $(PYTHON) setup.py $@
+
+clean:
+       -rm -rf build dist
+       -rm -f *.pyc *.pyo */*.pyc */*.pyo */*/*.pyc */*/*.pyo
+       -rm -f MANIFEST
+       cd src && $(MAKE) clean
+
+distclean: clean
+
+maintainer-clean: distclean
+
+dist sdist: distclean
+       $(PYTHON) setup.py sdist
+
+# target for maintainer
+windist: distclean
+       rm -rf build
+       python2.2 setup.py bdist_wininst
+       rm -rf build
+       python2.3 setup.py bdist_wininst
+       rm -rf build
+       python2.4 setup.py bdist_wininst
+       rm -rf build
+       python2.2 setup_win32_ssl.py bdist_wininst
+       rm -rf build
+       python2.3 setup_win32_ssl.py bdist_wininst
+       rm -rf build
+       python2.4 setup_win32_ssl.py bdist_wininst
+       rm -rf build
+
+
+.PHONY: all build test strip install install_lib clean distclean maintainer-clean dist sdist windist
+
+.NOEXPORT:
diff --git a/pycurl/PKG-INFO b/pycurl/PKG-INFO
new file mode 100644 (file)
index 0000000..f0c0e97
--- /dev/null
@@ -0,0 +1,11 @@
+Metadata-Version: 1.0
+Name: pycurl
+Version: 7.13.1
+Summary: PycURL -- cURL library module for Python
+Home-page: http://pycurl.sourceforge.net/
+Author: Kjetil Jacobsen, Markus F.X.J. Oberhumer
+Author-email: kjetilja@cs.uit.no, markus@oberhumer.com
+License: GNU Lesser General Public License (LGPL)
+Description: 
+        This module provides Python bindings for the cURL library.
+Platform: All
diff --git a/pycurl/README b/pycurl/README
new file mode 100644 (file)
index 0000000..bd04ab6
--- /dev/null
@@ -0,0 +1,12 @@
+LICENSE
+-------
+
+Copyright (C) 2001-2005 by Kjetil Jacobsen <kjetilja@cs.uit.no>
+Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer <markus@oberhumer.com>
+
+PycURL is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+A full copy of the LGPL license is included in the file COPYING.
diff --git a/pycurl/TODO b/pycurl/TODO
new file mode 100644 (file)
index 0000000..7541535
--- /dev/null
@@ -0,0 +1,27 @@
+# $Id: TODO 5574 2007-10-25 20:33:17Z thierry $
+# vi:ts=4:et
+
+If you want to hack on pycurl, here's our list of unresolved issues:
+
+
+NEW FEATURES/IMPROVEMENTS:
+
+    * Add docs to the high-level interface.
+
+    * Add more options to the undocumented and currently mostly useless
+      Curl.unsetopt() method. Have to carefully check the libcurl source
+      code for each option we want to support.
+
+    * curl_easy_reset() should probably be supported.  But we have to be
+      careful since curl_easy_reset() e.g. modifies callbacks and other
+      pointers which could leave pycurl and libcurl out of sync.
+
+
+DEFICIENICES:
+
+    * Using certain invalid options, it may be possible to cause a crash.
+      This is un-Pythonic behaviour, but you somewhere have to draw a line
+      between efficiency (and feature completeness) and safety.
+      There _are_ quite a number of internal error checks, but tracking and
+      catching all possible (deliberate) misuses is not a goal (and probably
+      impossible anyway, due to the complexity of libcurl).
diff --git a/pycurl/doc/callbacks.html b/pycurl/doc/callbacks.html
new file mode 100644 (file)
index 0000000..c808646
--- /dev/null
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PyCurl: Callbacks</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<h1>Callbacks</h1>
+
+<p>For more fine-grained control, libcurl allows a
+number of callbacks to be associated with each connection. In
+pycurl, callbacks are defined using the <code>setopt()</code> method for
+Curl objects with options WRITEFUNCTION, READFUNCTION, HEADERFUNCTION,
+PROGRESSFUNCTION, IOCTLFUNCTION, or DEBUGFUNCTION. These options
+correspond to the libcurl options with CURLOPT_* prefix removed.  A
+callback in pycurl must be either a regular Python function, a class
+method or an extension type function.</p>
+
+<p>There are some limitations to some of the options which can be used
+concurrently with the pycurl callbacks compared to the libcurl callbacks.
+This is to allow different callback functions to be associated with
+different Curl objects.  More specifically, WRITEDATA cannot
+be used with WRITEFUNCTION, READDATA cannot be used with READFUNCTION,
+WRITEHEADER cannot be used with HEADERFUNCTION, PROGRESSDATA cannot be
+used with PROGRESSFUNCTION, IOCTLDATA cannot be used with IOCTLFUNCTION,
+and DEBUGDATA cannot be used with DEBUGFUNCTION.
+In practice, these limitations can be overcome by having a callback
+function be a class instance method and rather use the class instance
+attributes to store per object data such as files used in the callbacks.
+</p>
+
+The signature of each callback used in pycurl is as follows:<br/>
+<br/>
+<code>WRITEFUNCTION(</code><em>string</em><code>) </code><em>-&gt; number of characters written<br/>
+</em>
+<br/>
+<code>READFUNCTION(</code><em>number of characters to read</em><code>)</code><em>-&gt;
+string</em><br/>
+<br/>
+<code>HEADERFUNCTION(</code><em>string</em><code>)</code><em> -&gt; number of characters written<br/>
+</em><br/>
+<code>PROGRESSFUNCTION(</code><em>download total, downloaded, upload total, uploaded</em><code>) </code><em>-&gt; status</em><br/>
+<br/>
+<code>DEBUGFUNCTION(</code><em>debug message type, debug message string</em><code>)</code>
+<em>-&gt; None<br/></em>
+<br/>
+<code>IOCTLFUNCTION(</code><em>ioctl cmd</em><code>)</code>
+<em>-&gt; status<br/></em>
+<br/>
+<hr/>
+
+<h2>Example: Callbacks for document header and body</h2>
+
+<p>This example prints the header data to stderr and the body data to
+stdout.  Also note that neither callback returns the number of bytes
+written.  For WRITEFUNCTION and HEADERFUNCTION callbacks, returning
+None implies that all bytes where written.</p>
+
+<pre>
+    ## Callback function invoked when body data is ready
+    def body(buf):
+        # Print body data to stdout
+        import sys
+        sys.stdout.write(buf)
+        # Returning None implies that all bytes were written
+
+    ## Callback function invoked when header data is ready
+    def header(buf):
+        # Print header data to stderr
+        import sys
+        sys.stderr.write(buf)
+        # Returning None implies that all bytes were written
+
+    c = pycurl.Curl()
+    c.setopt(pycurl.URL, "http://www.python.org/")
+    c.setopt(pycurl.WRITEFUNCTION, body)
+    c.setopt(pycurl.HEADERFUNCTION, header)
+    c.perform()
+</pre>
+
+<h2>Example: Download/upload progress callback</h2>
+
+<p>This example shows how to use the progress callback.  When downloading
+a document, the arguments related to uploads are zero, and vice versa.</p>
+
+<pre>
+    ## Callback function invoked when download/upload has progress
+    def progress(download_t, download_d, upload_t, upload_d):
+        print "Total to download", download_t
+        print "Total downloaded", download_d
+        print "Total to upload", upload_t
+        print "Total uploaded", upload_d
+
+    c.setopt(c.URL, "http://slashdot.org/")
+    c.setopt(c.NOPROGRESS, 0)
+    c.setopt(c.PROGRESSFUNCTION, progress)
+    c.perform()
+</pre>
+
+<h2>Example: Debug callbacks</h2>
+
+<p>This example shows how to use the debug callback.  The debug message
+type is an integer indicating the type of debug message.  The
+VERBOSE option must be enabled for this callback to be invoked.</p>
+
+<pre>
+    def test(debug_type, debug_msg):
+        print "debug(%d): %s" % (debug_type, debug_msg)
+
+    c = pycurl.Curl()
+    c.setopt(pycurl.URL, "http://curl.haxx.se/")
+    c.setopt(pycurl.VERBOSE, 1)
+    c.setopt(pycurl.DEBUGFUNCTION, test)
+    c.perform()
+</pre>
+
+<h2>Other examples</h2>
+The pycurl distribution also contains a number of test scripts and
+examples which show how to use the various callbacks in libcurl.
+For instance, the file 'examples/file_upload.py' in the distribution contains
+example code for using READFUNCTION, 'tests/test_cb.py' shows
+WRITEFUNCTION and HEADERFUNCTION, 'tests/test_debug.py' shows DEBUGFUNCTION,
+and 'tests/test_getinfo.py' shows PROGRESSFUNCTION.</p>
+
+
+<hr />
+<p>
+  <a href="http://validator.w3.org/check/referer"><img align="right"
+     src="http://www.w3.org/Icons/valid-xhtml10"
+     alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
+  $Id: callbacks.html 5574 2007-10-25 20:33:17Z thierry $
+</p>
+
+</body>
+</html>
diff --git a/pycurl/doc/curlmultiobject.html b/pycurl/doc/curlmultiobject.html
new file mode 100644 (file)
index 0000000..7af3d54
--- /dev/null
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PycURL: CurlMulti Objects</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<h1>CurlMulti Object</h1>
+
+<p>CurlMulti objects have the following methods: </p>
+
+<dl>
+<dt><code>close()</code> -&gt; <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_cleanup.html"><code>curl_multi_cleanup()</code></a> in libcurl.
+This method is automatically called by pycurl when a CurlMulti object no
+longer has any references to it, but can also be called
+explicitly.</p>
+</dd>
+
+<dt><code>perform()</code> -&gt; <em>tuple of status and the number of active Curl objects</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_perform.html"><code>curl_multi_perform()</code></a> in libcurl.</p>
+</dd>
+
+<dt><code> add_handle(</code><em>Curl object</em><code>) </code>-&gt; <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_add_handle.html"><code>curl_multi_add_handle()</code></a> in libcurl.
+This method  adds an existing and valid Curl object to the CurlMulti
+object.</p>
+
+<p>IMPORTANT NOTE: add_handle does not implicitly add a Python reference
+to the Curl object (and thus does not increase the reference count on the Curl
+object).</p>
+</dd>
+
+<dt><code>remove_handle(</code><em>Curl object</em><code>)</code> -&gt; <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_remove_handle.html"><code>curl_multi_remove_handle()</code></a> in libcurl.
+This method removes an existing and valid Curl object from the CurlMulti
+object.</p>
+
+<p>IMPORTANT NOTE: remove_handle does not implicitly remove a Python reference
+from the Curl object (and thus does not decrease the reference count on the Curl
+object).</p>
+</dd>
+
+<dt><code>fdset()</code> -&gt;
+<em>triple of lists with active file descriptors,
+readable,  writeable, exceptions.</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_fdset.html"><code>curl_multi_fdset()</code></a> in libcurl.
+This method extracts  the file descriptor information from a CurlMulti object.
+The returned  lists can be used with the <code>select</code> module to
+poll for events.</p>
+
+<p>Example usage:</p>
+
+<pre>
+import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://curl.haxx.se")
+m = pycurl.CurlMulti()
+m.add_handle(c)
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM: break
+while num_handles:
+    apply(select.select, m.fdset() + (1,))
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM: break
+</pre>
+</dd>
+
+<dt><code>select(</code><em>[timeout]</em><code>)</code> -&gt;
+<em>number of ready file descriptors or -1 on timeout</em></dt>
+<dd>
+<p>This is a convenience function which simplifies the combined
+use of <code>fdset()</code> and the <code>select</code> module.</p>
+
+<p>Example usage:</p>
+
+<pre>import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://curl.haxx.se")
+m = pycurl.CurlMulti()
+m.add_handle(c)
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM: break
+while num_handles:
+    ret = m.select()
+    if ret == -1:  continue
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM: break
+</pre>
+</dd>
+
+<dt><code>info_read(</code><em>[max]</em><code>)</code> -&gt;
+<em>numberof queued messages, a list of successful objects, a list of
+failed objects</em></dt>
+<dd>
+<p>Corresponds to the
+<a href="http://curl.haxx.se/libcurl/c/curl_multi_info_read.html"><code>curl_multi_info_read()</code></a> function in libcurl.
+This method extracts at most <em>max</em> messages
+from the multi stack and returns them in two lists. The first
+list contains the handles which completed successfully and the second
+list contains a tuple <em>&lt;curl object, curl error number, curl
+error message&gt;</em> for each failed curl object. The number
+of queued messages after this method has been called is also
+returned.</p>
+</dd>
+</dl>
+
+<hr />
+<p>
+  <a href="http://validator.w3.org/check/referer"><img align="right"
+     src="http://www.w3.org/Icons/valid-xhtml10"
+     alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
+  $Id: curlmultiobject.html 5574 2007-10-25 20:33:17Z thierry $
+</p>
+
+</body>
+</html>
diff --git a/pycurl/doc/curlobject.html b/pycurl/doc/curlobject.html
new file mode 100644 (file)
index 0000000..b6ad774
--- /dev/null
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PycURL: Curl Objects</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<h1>Curl Object</h1>
+
+<p>Curl objects have the following methods:</p>
+
+<dl>
+<dt><code>close()</code> -&gt; <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_cleanup.html"><code>curl_easy_cleanup</code></a> in libcurl.
+This method is automatically called by pycurl when a Curl object no longer has
+any references to it, but can also be called explicitly.</p>
+</dd>
+
+<dt><code>perform()</code> -&gt; <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_perform.html"><code>curl_easy_perform</code></a> in libcurl.</p>
+</dd>
+
+<dt><code>setopt(</code><em>option, value</em><code>)</code> -&gt; <em>None</em></dt>
+<dd>
+
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_setopt.html"><code>curl_easy_setopt</code></a> in libcurl, where
+<em>option</em> is specified with the CURLOPT_* constants in libcurl,
+except that the CURLOPT_ prefix has been removed. The type for
+<em>value</em> depends on the option, and can be either a string,
+integer, long integer, file objects, lists, or functions.</p>
+
+<p>Example usage:</p>
+
+<pre>
+import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://www.python.org/")
+c.setopt(pycurl.HTTPHEADER, ["Accept:"])
+import StringIO
+b = StringIO.StringIO()
+c.setopt(pycurl.WRITEFUNCTION, b.write)
+c.setopt(pycurl.FOLLOWLOCATION, 1)
+c.setopt(pycurl.MAXREDIRS, 5)
+c.perform()
+print b.getvalue()
+...
+</pre>
+</dd>
+
+<dt><code>getinfo(</code><em>option</em><code>) </code>-&gt; <em>Result</em></dt>
+<dd>
+
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html"><code>curl_easy_getinfo</code></a> in libcurl, where
+<em>option</em> is the same as the CURLINFO_* constants in libcurl,
+except that the CURLINFO_ prefix has been removed.
+<em>Result</em> contains an integer, float or string, depending on
+which option is given. The <code>getinfo</code> method should
+not be called unless <code>perform</code> has been called and
+finished.</p>
+
+<p>Example usage:</p>
+
+<pre>
+import pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, "http://sf.net")
+c.setopt(pycurl.FOLLOWLOCATION, 1)
+c.perform()
+print c.getinfo(pycurl.HTTP_CODE), c.getinfo(pycurl.EFFECTIVE_URL)
+...
+--&gt; 200 "http://sourceforge.net/"
+</pre>
+</dd>
+
+<dt><code>errstr()</code> -&gt; <em>String</em></dt>
+<dd>
+<p>Returns the internal libcurl error buffer of this handle as a string.</p>
+</dd>
+</dl>
+
+
+<hr />
+<p>
+  <a href="http://validator.w3.org/check/referer"><img align="right"
+     src="http://www.w3.org/Icons/valid-xhtml10"
+     alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
+  $Id: curlobject.html 5574 2007-10-25 20:33:17Z thierry $
+</p>
+
+</body>
+</html>
diff --git a/pycurl/doc/pycurl.html b/pycurl/doc/pycurl.html
new file mode 100644 (file)
index 0000000..7d796d0
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>PycURL Documentation</title>
+  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
+  <meta name="revisit-after" content="30 days" />
+  <meta name="robots" content="noarchive, index, follow" />
+</head>
+<body>
+
+<h1><tt>pycurl</tt> &mdash; A Python interface to the cURL library</h1>
+
+<p>The pycurl package is a Python interface to libcurl (<a
+href="http://curl.haxx.se/libcurl/">http://curl.haxx.se/libcurl/</a>). pycurl
+has been successfully built and tested with Python versions from
+2.2 to the current 2.4.x releases.</p>
+
+<p>libcurl is a client-side URL transfer library supporting FTP, FTPS,
+HTTP, HTTPS, GOPHER, TELNET, DICT, FILE and LDAP.  libcurl
+also supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploads, proxies,
+cookies, basic authentication, file transfer resume of FTP sessions, HTTP
+proxy tunneling and more.</p>
+
+<p>All the functionality provided by libcurl can used through the
+pycurl interface. The following subsections describe how to use the
+pycurl interface, and assume familiarity with how libcurl works.  For
+information on how libcurl works, please consult the curl library web pages
+(<a href="http://curl.haxx.se/libcurl/c/">http://curl.haxx.se/libcurl/c/</a>).</p>
+
+<hr/>
+
+<h1>Module Functionality</h1>
+
+<dl>
+<dt><code>pycurl.global_init(</code><em>option</em><code>)</code> -&gt;<em>None</em></dt>
+
+<dd><p><em>option</em> is one of the constants
+pycurl.GLOBAL_SSL, pycurl.GLOBAL_WIN32, pycurl.GLOBAL_ALL,
+pycurl.GLOBAL_NOTHING, pycurl.GLOBAL_DEFAULT.  Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_global_init.html"><code>curl_global_init()</code></a> in libcurl.</p>
+</dd>
+
+<dt><code>pycurl.global_cleanup()</code> -&gt; <em>None</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_global_cleanup.html"><code>curl_global_cleanup()</code></a> in libcurl.</p>
+</dd>
+
+<dt><code>pycurl.version</code></dt>
+
+<dd><p>This is a string with version information on libcurl,
+corresponding to
+<a href="http://curl.haxx.se/libcurl/c/curl_version.html"><code>curl_version()</code></a> in libcurl.</p>
+
+<p>Example usage:</p>
+<pre>
+>>> import pycurl
+>>> pycurl.version
+'libcurl/7.12.3 OpenSSL/0.9.7e zlib/1.2.2.1 libidn/0.5.12'
+</pre>
+</dd>
+
+<dt><code>pycurl.version_info()</code> -&gt; <em>Tuple</em></dt>
+<dd>
+<p>Corresponds to
+<a href="http://curl.haxx.se/libcurl/c/curl_version_info.html"><code>curl_version_info()</code></a> in libcurl.
+Returns a tuple of information which is similar to the
+<code>curl_version_info_data</code> struct returned by
+<code>curl_version_info()</code> in libcurl.</p>
+
+<p>Example usage:</p>
+<pre>
+>>> import pycurl
+>>> pycurl.version_info()
+(2, '7.12.3', 461827, 'i586-pc-linux-gnu', 1565, 'OpenSSL/0.9.7e', 9465951,
+'1.2.2.1', ('ftp', 'gopher', 'telnet', 'dict', 'ldap', 'http', 'file',
+'https', 'ftps'), None, 0, '0.5.12')
+</pre>
+</dd>
+
+<dt><code>pycurl.Curl()</code> -&gt; <em>Curl object</em></dt>
+<dd>
+<p>This function creates a new
+<a href="curlobject.html">Curl object</a> which corresponds to a
+<code>CURL</code> handle in libcurl. Curl objects automatically
+set CURLOPT_VERBOSE to 0, CURLOPT_NOPROGRESS to 1,
+provide a default CURLOPT_USERAGENT and setup
+CURLOPT_ERRORBUFFER to point to a private error buffer.</p>
+</dd>
+
+<dt><code>pycurl.CurlMulti()</code> -&gt; <em>CurlMulti object</em></dt>
+<dd>
+<p>This function creates a new
+<a href="curlmultiobject.html">CurlMulti object</a> which corresponds to
+a <code>CURLM</code> handle in libcurl.</p>
+</dd>
+</dl>
+
+<hr/>
+
+<h1>Subsections</h1>
+
+<ul>
+  <li><a href="curlobject.html">Curl objects</a></li>
+  <li><a href="curlmultiobject.html">CurlMulti objects</a></li>
+  <li><a href="callbacks.html">Callbacks</a></li>
+</ul>
+
+<hr />
+<p>
+  <a href="http://validator.w3.org/check/referer"><img align="right"
+     src="http://www.w3.org/Icons/valid-xhtml10"
+     alt="Valid XHTML 1.0!" height="31" width="88" border="0" /></a>
+  $Id: pycurl.html 5574 2007-10-25 20:33:17Z thierry $
+</p>
+
+</body>
+</html>
diff --git a/pycurl/examples/basicfirst.py b/pycurl/examples/basicfirst.py
new file mode 100644 (file)
index 0000000..5bed8b5
--- /dev/null
@@ -0,0 +1,25 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: basicfirst.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys
+import pycurl
+
+class Test:
+    def __init__(self):
+        self.contents = ''
+
+    def body_callback(self, buf):
+        self.contents = self.contents + buf
+
+print >>sys.stderr, 'Testing', pycurl.version
+
+t = Test()
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://curl.haxx.se/dev/')
+c.setopt(c.WRITEFUNCTION, t.body_callback)
+c.perform()
+c.close()
+
+print t.contents
diff --git a/pycurl/examples/file_upload.py b/pycurl/examples/file_upload.py
new file mode 100644 (file)
index 0000000..2734d5a
--- /dev/null
@@ -0,0 +1,46 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: file_upload.py 5574 2007-10-25 20:33:17Z thierry $
+
+import os, sys
+import pycurl
+
+# Class which holds a file reference and the read callback
+class FileReader:
+    def __init__(self, fp):
+        self.fp = fp
+    def read_callback(self, size):
+        return self.fp.read(size)
+
+# Check commandline arguments
+if len(sys.argv) < 3:
+    print "Usage: %s <url> <file to upload>" % sys.argv[0]
+    raise SystemExit
+url = sys.argv[1]
+filename = sys.argv[2]
+
+if not os.path.exists(filename):
+    print "Error: the file '%s' does not exist" % filename
+    raise SystemExit
+
+# Initialize pycurl
+c = pycurl.Curl()
+c.setopt(pycurl.URL, url)
+c.setopt(pycurl.UPLOAD, 1)
+
+# Two versions with the same semantics here, but the filereader version
+# is useful when you have to process the data which is read before returning
+if 1:
+    c.setopt(pycurl.READFUNCTION, FileReader(open(filename, 'rb')).read_callback)
+else:
+    c.setopt(pycurl.READFUNCTION, open(filename, 'rb').read)
+
+# Set size of file to be uploaded.
+filesize = os.path.getsize(filename)
+c.setopt(pycurl.INFILESIZE, filesize)
+
+# Start transfer
+print 'Uploading file %s to url %s' % (filename, url)
+c.perform()
+c.close()
diff --git a/pycurl/examples/linksys.py b/pycurl/examples/linksys.py
new file mode 100755 (executable)
index 0000000..a60eba1
--- /dev/null
@@ -0,0 +1,563 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+#
+# linksys.py -- program settings on a Linkys router
+#
+# This tool is designed to help you recover from the occasional episodes
+# of catatonia that afflict Linksys boxes. It allows you to batch-program
+# them rather than manually entering values to the Web interface.  Commands
+# are taken from the command line first, then standard input.
+#
+# The somewhat spotty coverage of status queries is because I only did the
+# ones that were either (a) easy, or (b) necessary.  If you want to know the
+# status of the box, look at the web interface.
+#
+# This code has been tested against the following hardware:
+#
+#   Hardware    Firmware
+#   ----------  ---------------------
+#   BEFW11S4v2  1.44.2.1, Dec 20 2002
+#
+# The code is, of course, sensitive to changes in the names of CGI pages
+# and field names.
+#
+# Note: to make the no-arguments form work, you'll need to have the following
+# entry in your ~/.netrc file.  If you have changed the router IP address or
+# name/password, modify accordingly.
+#
+# machine 192.168.1.1
+#   login ""
+#   password admin
+#
+# By Eric S. Raymond, August April 2003.  All rites reversed.
+
+import sys, re, copy, curl, exceptions
+
+class LinksysError(exceptions.Exception):
+    def __init__(self, *args):
+        self.args = args
+
+class LinksysSession:
+    months = 'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec'
+
+    WAN_CONNECT_AUTO = '1'
+    WAN_CONNECT_STATIC = '2'
+    WAN_CONNECT_PPOE = '3'
+    WAN_CONNECT_RAS = '4'
+    WAN_CONNECT_PPTP = '5'
+    WAN_CONNECT_HEARTBEAT = '6'
+
+    # Substrings to check for on each page load.
+    # This may enable us to detect when a firmware change has hosed us.
+    check_strings = {
+        "":           "basic setup functions",
+        "Passwd.htm": "For security reasons,",
+        "DHCP.html":  "You can configure the router to act as a DHCP",
+        "Log.html":   "There are some log settings and lists in this page.",
+        "Forward.htm":"Port forwarding can be used to set up public services",
+        }
+
+    def __init__(self):
+        self.actions = []
+        self.host = "http://192.168.1.1"
+        self.verbosity = False
+        self.pagecache = {}
+
+    def set_verbosity(self, flag):
+        self.verbosity = flag
+
+    # This is not a performance hack -- we need the page cache to do
+    # sanity checks at configure time.
+    def cache_load(self, page):
+        if page not in self.pagecache:
+            fetch = curl.Curl(self.host)
+            fetch.set_verbosity(self.verbosity)
+            fetch.get(page)
+            self.pagecache[page] = fetch.body()
+            if fetch.answered("401"):
+                raise LinksysError("authorization failure.", True)
+            elif not fetch.answered(LinksysSession.check_strings[page]):
+                del self.pagecache[page]
+                raise LinksysError("check string for page %s missing!" % os.path.join(self.host, page), False)
+            fetch.close()
+    def cache_flush(self):
+        self.pagecache = {}
+
+    # Primitives
+    def screen_scrape(self, page, template):
+        self.cache_load(page)
+        match = re.compile(template).search(self.pagecache[page])
+        if match:
+            result = match.group(1)
+        else:
+            result = None
+        return result
+    def get_MAC_address(self, page, prefix):
+        return self.screen_scrape("", prefix+r":[^M]*\(MAC Address: *([^)]*)")
+    def set_flag(page, flag, value):
+        if value:
+            self.actions.append(page, flag, "1")
+        else:
+            self.actions.append(page, flag, "0")
+    def set_IP_address(self, page, cgi, role, ip):
+        ind = 0
+        for octet in ip.split("."):
+            self.actions.append(("", "F1", role + `ind+1`, octet))
+            ind += 1
+
+    # Scrape configuration data off the main page
+    def get_firmware_version(self):
+        # This is fragile.  There is no distinguishing tag before the firmware
+        # version, so we have to key off the pattern of the version number.
+        # Our model is ">1.44.2.1, Dec 20 2002<"
+        return self.screen_scrape("", ">([0-9.v]*, (" + \
+                                  LinksysSession.months + ")[^<]*)<", )
+    def get_LAN_MAC(self):
+        return self.get_MAC_address("", r"LAN IP Address")
+    def get_Wireless_MAC(self):
+        return self.get_MAC_address("", r"Wireless")
+    def get_WAN_MAC(self):
+        return self.get_MAC_address("", r"WAN Connection Type")
+
+    # Set configuration data on the main page
+    def set_host_name(self, name):
+        self.actions.append(("", "hostName", name))
+    def set_domain_name(self, name):
+        self.actions.append(("", "DomainName", name))
+    def set_LAN_IP(self, ip):
+        self.set_IP_address("", "ipAddr", ip)
+    def set_LAN_netmask(self, ip):
+        if not ip.startswith("255.255.255."):
+            raise ValueError
+        lastquad = ip.split(".")[-1]
+        if lastquad not in ("0", "128", "192", "240", "252"):
+            raise ValueError
+        self.actions.append("", "netMask", lastquad)
+    def set_wireless(self, flag):
+        self.set_flag("", "wirelessStatus")
+    def set_SSID(self, ssid):
+        self.actions.append(("", "wirelessESSID", ssid))
+    def set_SSID_broadcast(self, flag):
+        self.set_flag("", "broadcastSSID")
+    def set_channel(self, channel):
+        self.actions.append(("", "wirelessChannel", channel))
+    def set_WEP(self, flag):
+        self.set_flag("", "WepType")
+    # FIXME: Add support for setting WEP keys
+    def set_connection_type(self, type):
+        self.actions.append(("", "WANConnectionType", type))
+    def set_WAN_IP(self, ip):
+        self.set_IP_address("", "aliasIP", ip)
+    def set_WAN_netmask(self, ip):
+        self.set_IP_address("", "aliasMaskIP", ip)
+    def set_WAN_gateway_address(self, ip):
+        self.set_IP_address("", "routerIP", ip)
+    def set_DNS_server(self, index, ip):
+        self.set_IP_address("", "dns" + "ABC"[index], ip)
+
+    # Set configuration data on the password page
+    def set_password(self, str):
+        self.actions.append("Passwd.htm","sysPasswd", str)
+        self.actions.append("Passwd.htm","sysPasswdConfirm", str)
+    def set_UPnP(self, flag):
+        self.set_flag("Passwd.htm", "UPnP_Work")
+    def reset(self):
+        self.actions.append("Passwd.htm", "FactoryDefaults")
+
+    # DHCP features
+    def set_DHCP(self, flag):
+        if flag:
+            self.actions.append("DHCP.htm","dhcpStatus","Enable")
+        else:
+            self.actions.append("DHCP.htm","dhcpStatus","Disable")
+    def set_DHCP_starting_IP(self, val):
+        self.actions.append("DHCP.htm","dhcpS4", str(val))
+    def set_DHCP_users(self, val):
+        self.actions.append("DHCP.htm","dhcpLen", str(val))
+    def set_DHCP_lease_time(self, val):
+        self.actions.append("DHCP.htm","leaseTime", str(val))
+    def set_DHCP_DNS_server(self, index, ip):
+        self.set_IP_address("DHCP.htm", "dns" + "ABC"[index], ip)
+    # FIXME: add support for setting WINS key
+
+    # Logging features
+    def set_logging(self, flag):
+        if flag:
+            self.actions.append("Log.htm", "rLog", "Enable")
+        else:
+            self.actions.append("Log.htm", "rLog", "Disable")
+    def set_log_address(self, val):
+        self.actions.append("DHCP.htm","trapAddr3", str(val))
+
+    # The AOL parental control flag is not supported by design.
+
+    # FIXME: add Filters and other advanced features
+
+    def configure(self):
+        "Write configuration changes to the Linksys."
+        if self.actions:
+            fields = []
+            self.cache_flush()
+            for (page, field, value) in self.actions:
+                self.cache_load(page)
+                if self.pagecache[page].find(field) == -1:
+                    print >>sys.stderr, "linksys: field %s not found where expected in page %s!" % (field, os.path.join(self.host, page))
+                    continue
+                else:
+                    fields.append((field, value))
+            # Clearing the action list before fieldsping is deliberate.
+            # Otherwise we could get permanently wedged by a 401.
+            self.actions = []
+            transaction = curl.Curl(self.host)
+            transaction.set_verbosity(self.verbosity)
+            transaction.get("Gozila.cgi", tuple(fields))
+            transaction.close()
+
+if __name__ == "__main__":
+    import os, cmd
+
+    class LinksysInterpreter(cmd.Cmd):
+        """Interpret commands to perform LinkSys programming actions."""
+        def __init__(self):
+            self.session = LinksysSession()
+            if os.isatty(0):
+                import readline
+                print "Type ? or `help' for help."
+                self.prompt = self.session.host + ": "
+            else:
+                self.prompt = ""
+                print "Bar1"
+
+        def flag_command(self, func):
+            if line.strip() in ("on", "enable", "yes"):
+                func(True)
+            elif line.strip() in ("off", "disable", "no"):
+                func(False)
+            else:
+                print >>sys.stderr, "linksys: unknown switch value"
+            return 0
+
+        def do_connect(self, line):
+            newhost = line.strip()
+            if newhost:
+                self.session.host = newhost
+                self.session.cache_flush()
+                self.prompt = self.session.host + ": "
+            else:
+                print self.session.host
+            return 0
+        def help_connect(self):
+            print "Usage: connect [<hostname-or-IP>]"
+            print "Connect to a Linksys by name or IP address."
+            print "If no argument is given, print the current host."
+
+        def do_status(self, line):
+            self.session.cache_load("")
+            if "" in self.session.pagecache:
+                print "Firmware:", self.session.get_firmware_version()
+                print "LAN MAC:", self.session.get_LAN_MAC()
+                print "Wireless MAC:", self.session.get_Wireless_MAC()
+                print "WAN MAC:", self.session.get_WAN_MAC()
+                print "."
+            return 0
+        def help_status(self):
+            print "Usage: status"
+            print "The status command shows the status of the Linksys."
+            print "It is mainly useful as a sanity check to make sure"
+            print "the box is responding correctly."
+
+        def do_verbose(self, line):
+            self.flag_command(self.session.set_verbosity)
+        def help_verbose(self):
+            print "Usage: verbose {on|off|enable|disable|yes|no}"
+            print "Enables display of HTTP requests."
+
+        def do_host(self, line):
+            self.session.set_host_name(line)
+            return 0
+        def help_host(self):
+            print "Usage: host <hostname>"
+            print "Sets the Host field to be queried by the ISP."
+
+        def do_domain(self, line):
+            print "Usage: host <domainname>"
+            self.session.set_domain_name(line)
+            return 0
+        def help_domain(self):
+            print "Sets the Domain field to be queried by the ISP."
+
+        def do_lan_address(self, line):
+            self.session.set_LAN_IP(line)
+            return 0
+        def help_lan_address(self):
+            print "Usage: lan_address <ip-address>"
+            print "Sets the LAN IP address."
+
+        def do_lan_netmask(self, line):
+            self.session.set_LAN_netmask(line)
+            return 0
+        def help_lan_netmask(self):
+            print "Usage: lan_netmask <ip-mask>"
+            print "Sets the LAN subnetwork mask."
+
+        def do_wireless(self, line):
+            self.flag_command(self.session.set_wireless)
+            return 0
+        def help_wireless(self):
+            print "Usage: wireless {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable wireless features."
+
+        def do_ssid(self, line):
+            self.session.set_SSID(line)
+            return 0
+        def help_ssid(self):
+            print "Usage: ssid <string>"
+            print "Sets the SSID used to control wireless access."
+
+        def do_ssid_broadcast(self, line):
+            self.flag_command(self.session.set_SSID_broadcast)
+            return 0
+        def help_ssid_broadcast(self):
+            print "Usage: ssid_broadcast {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable SSID broadcast."
+
+        def do_channel(self, line):
+            self.session.set_channel(line)
+            return 0
+        def help_channel(self):
+            print "Usage: channel <number>"
+            print "Sets the wireless channel."
+
+        def do_wep(self, line):
+            self.flag_command(self.session.set_WEP)
+            return 0
+        def help_wep(self):
+            print "Usage: wep {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable WEP security."
+
+        def do_wan_type(self, line):
+            try:
+                type=eval("LinksysSession.WAN_CONNECT_"+line.strip().upper())
+                self.session.set_connection_type(type)
+            except ValueError:
+                print >>sys.stderr, "linksys: unknown connection type."
+            return 0
+        def help_wan_type(self):
+            print "Usage: wan_type {auto|static|ppoe|ras|pptp|heartbeat}"
+            print "Set the WAN connection type."
+
+        def do_wan_address(self, line):
+            self.session.set_WAN_IP(line)
+            return 0
+        def help_wan_address(self):
+            print "Usage: wan_address <ip-address>"
+            print "Sets the WAN IP address."
+
+        def do_wan_netmask(self, line):
+            self.session.set_WAN_netmask(line)
+            return 0
+        def help_wan_netmask(self):
+            print "Usage: wan_netmask <ip-mask>"
+            print "Sets the WAN subnetwork mask."
+
+        def do_wan_gateway(self, line):
+            self.session.set_WAN_gateway(line)
+            return 0
+        def help_wan_gateway(self):
+            print "Usage: wan_gateway <ip-address>"
+            print "Sets the LAN subnetwork mask."
+
+        def do_dns(self, line):
+            (index, address) = line.split()
+            if index in ("1", "2", "3"):
+                self.session.set_DNS_server(eval(index), address)
+            else:
+                print >>sys.stderr, "linksys: server index out of bounds."
+            return 0
+        def help_dns(self):
+            print "Usage: dns {1|2|3} <ip-mask>"
+            print "Sets a primary, secondary, or tertiary DNS server address."
+
+        def do_password(self, line):
+            self.session.set_password(line)
+            return 0
+        def help_password(self):
+            print "Usage: password <string>"
+            print "Sets the router password."
+
+        def do_upnp(self, line):
+            self.flag_command(self.session.set_UPnP)
+            return 0
+        def help_upnp(self):
+            print "Usage: upnp {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable Universal Plug and Play."
+
+        def do_reset(self, line):
+            self.session.reset()
+        def help_reset(self):
+            print "Usage: reset"
+            print "Reset Linksys settings to factory defaults."
+
+        def do_dhcp(self, line):
+            self.flag_command(self.session.set_DHCP)
+        def help_dhcp(self):
+            print "Usage: dhcp {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable DHCP features."
+
+        def do_dhcp_start(self, line):
+            self.session.set_DHCP_starting_IP(line)
+        def help_dhcp_start(self):
+            print "Usage: dhcp_start <number>"
+            print "Set the start address of the DHCP pool."
+
+        def do_dhcp_users(self, line):
+            self.session.set_DHCP_users(line)
+        def help_dhcp_users(self):
+            print "Usage: dhcp_users <number>"
+            print "Set number of address slots to allocate in the DHCP pool."
+
+        def do_dhcp_lease(self, line):
+            self.session.set_DHCP_lease(line)
+        def help_dhcp_lease(self):
+            print "Usage: dhcp_lease <number>"
+            print "Set number of address slots to allocate in the DHCP pool."
+
+        def do_dhcp_dns(self, line):
+            (index, address) = line.split()
+            if index in ("1", "2", "3"):
+                self.session.set_DHCP_DNS_server(eval(index), address)
+            else:
+                print >>sys.stderr, "linksys: server index out of bounds."
+            return 0
+        def help_dhcp_dns(self):
+            print "Usage: dhcp_dns {1|2|3} <ip-mask>"
+            print "Sets primary, secondary, or tertiary DNS server address."
+
+        def do_logging(self, line):
+            self.flag_command(self.session.set_logging)
+        def help_logging(self):
+            print "Usage: logging {on|off|enable|disable|yes|no}"
+            print "Switch to enable or disable session logging."
+
+        def do_log_address(self, line):
+            self.session.set_Log_address(line)
+        def help_log_address(self):
+            print "Usage: log_address <number>"
+            print "Set the last quad of the address to which to log."
+
+        def do_configure(self, line):
+            self.session.configure()
+            return 0
+        def help_configure(self):
+            print "Usage: configure"
+            print "Writes the configuration to the Linksys."
+
+        def do_cache(self, line):
+            print self.session.pagecache
+        def help_cache(self):
+            print "Usage: cache"
+            print "Display the page cache."
+
+        def do_quit(self, line):
+            return 1
+        def help_quit(self, line):
+            print "The quit command ends your linksys session without"
+            print "writing configuration changes to the Linksys."
+        def do_EOF(self, line):
+            print ""
+            self.session.configure()
+            return 1
+        def help_EOF(self):
+            print "The EOF command writes the configuration to the linksys"
+            print "and ends your session."
+
+        def default(self, line):
+            """Pass the command through to be executed by the shell."""
+            os.system(line)
+            return 0
+
+        def help_help(self):
+            print "On-line help is available through this command."
+            print "? is a convenience alias for help."
+
+        def help_introduction(self):
+            print """\
+
+This program supports changing the settings on Linksys blue-box routers.  This
+capability may come in handy when they freeze up and have to be reset.  Though
+it can be used interactively (and will command-prompt when standard input is a
+terminal) it is really designed to be used in batch mode. Commands are taken
+from the command line first, then standard input.
+
+By default, it is assumed that the Linksys is at http://192.168.1.1, the
+default LAN address.  You can connect to a different address or IP with the
+'connect' command.  Note that your .netrc must contain correct user/password
+credentials for the router.  The entry corresponding to the defaults is:
+
+machine 192.168.1.1
+    login ""
+    password admin
+
+Most commands queue up changes but don't actually send them to the Linksys.
+You can force pending changes to be written with 'configure'.  Otherwise, they
+will be shipped to the Linksys at the end of session (e.g.  when the program
+running in batch mode encounters end-of-file or you type a control-D).  If you
+end the session with `quit', pending changes will be discarded.
+
+For more help, read the topics 'wan', 'lan', and 'wireless'."""
+
+        def help_lan(self):
+            print """\
+The `lan_address' and `lan_netmask' commands let you set the IP location of
+the Linksys on your LAN, or inside.  Normally you'll want to leave these
+untouched."""
+
+        def help_wan(self):
+            print """\
+The WAN commands become significant if you are using the BEFSR41 or any of
+the other Linksys boxes designed as DSL or cable-modem gateways.  You will
+need to use `wan_type' to declare how you expect to get your address.
+
+If your ISP has issued you a static address, you'll need to use the
+`wan_address', `wan_netmask', and `wan_gateway' commands to set the address
+of the router as seen from the WAN, the outside. In this case you will also
+need to use the `dns' command to declare which remote servers your DNS
+requests should be forwarded to.
+
+Some ISPs may require you to set host and domain for use with dynamic-address
+allocation."""
+
+        def help_wireless(self):
+            print """\
+The channel, ssid, ssid_broadcast, wep, and wireless commands control
+wireless routing."""
+
+        def help_switches(self):
+            print "Switches may be turned on with 'on', 'enable', or 'yes'."
+            print "Switches may be turned off with 'off', 'disable', or 'no'."
+            print "Switch commands include: wireless, ssid_broadcast."
+
+        def help_addresses(self):
+            print "An address argument must be a valid IP address;"
+            print "four decimal numbers separated by dots, each "
+            print "between 0 and 255."
+
+        def emptyline(self):
+            pass
+
+    interpreter = LinksysInterpreter()
+    for arg in sys.argv[1:]:
+        interpreter.onecmd(arg)
+    fatal = False
+    while not fatal:
+        try:
+            interpreter.cmdloop()
+            fatal = True
+        except LinksysError, (message, fatal):
+            print "linksys:", message
+
+# The following sets edit modes for GNU EMACS
+# Local Variables:
+# mode:python
+# End:
diff --git a/pycurl/examples/retriever-multi.py b/pycurl/examples/retriever-multi.py
new file mode 100644 (file)
index 0000000..b8f3629
--- /dev/null
@@ -0,0 +1,122 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: retriever-multi.py 5574 2007-10-25 20:33:17Z thierry $
+
+#
+# Usage: python retriever-multi.py <file with URLs to fetch> [<# of
+#          concurrent connections>]
+#
+
+import sys
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+# Get args
+num_conn = 10
+try:
+    if sys.argv[1] == "-":
+        urls = sys.stdin.readlines()
+    else:
+        urls = open(sys.argv[1]).readlines()
+    if len(sys.argv) >= 3:
+        num_conn = int(sys.argv[2])
+except:
+    print "Usage: %s <file with URLs to fetch> [<# of concurrent connections>]" % sys.argv[0]
+    raise SystemExit
+
+
+# Make a queue with (url, filename) tuples
+queue = []
+for url in urls:
+    url = url.strip()
+    if not url or url[0] == "#":
+        continue
+    filename = "doc_%03d.dat" % (len(queue) + 1)
+    queue.append((url, filename))
+
+
+# Check args
+assert queue, "no URLs given"
+num_urls = len(queue)
+num_conn = min(num_conn, num_urls)
+assert 1 <= num_conn <= 10000, "invalid number of concurrent connections"
+print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
+print "----- Getting", num_urls, "URLs using", num_conn, "connections -----"
+
+
+# Pre-allocate a list of curl objects
+m = pycurl.CurlMulti()
+m.handles = []
+for i in range(num_conn):
+    c = pycurl.Curl()
+    c.fp = None
+    c.setopt(pycurl.FOLLOWLOCATION, 1)
+    c.setopt(pycurl.MAXREDIRS, 5)
+    c.setopt(pycurl.CONNECTTIMEOUT, 30)
+    c.setopt(pycurl.TIMEOUT, 300)
+    c.setopt(pycurl.NOSIGNAL, 1)
+    m.handles.append(c)
+
+
+# Main loop
+freelist = m.handles[:]
+num_processed = 0
+while num_processed < num_urls:
+    # If there is an url to process and a free curl object, add to multi stack
+    while queue and freelist:
+        url, filename = queue.pop(0)
+        c = freelist.pop()
+        c.fp = open(filename, "wb")
+        c.setopt(pycurl.URL, url)
+        c.setopt(pycurl.WRITEDATA, c.fp)
+        m.add_handle(c)
+        # store some info
+        c.filename = filename
+        c.url = url
+    # Run the internal curl state machine for the multi stack
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+    # Check for curl objects which have terminated, and add them to the freelist
+    while 1:
+        num_q, ok_list, err_list = m.info_read()
+        for c in ok_list:
+            c.fp.close()
+            c.fp = None
+            m.remove_handle(c)
+            print "Success:", c.filename, c.url, c.getinfo(pycurl.EFFECTIVE_URL)
+            freelist.append(c)
+        for c, errno, errmsg in err_list:
+            c.fp.close()
+            c.fp = None
+            m.remove_handle(c)
+            print "Failed: ", c.filename, c.url, errno, errmsg
+            freelist.append(c)
+        num_processed = num_processed + len(ok_list) + len(err_list)
+        if num_q == 0:
+            break
+    # Currently no more I/O is pending, could do something in the meantime
+    # (display a progress bar, etc.).
+    # We just call select() to sleep until some more data is available.
+    m.select()
+
+
+# Cleanup
+for c in m.handles:
+    if c.fp is not None:
+        c.fp.close()
+        c.fp = None
+    c.close()
+m.close()
+
diff --git a/pycurl/examples/retriever.py b/pycurl/examples/retriever.py
new file mode 100644 (file)
index 0000000..422a9e5
--- /dev/null
@@ -0,0 +1,99 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: retriever.py 5574 2007-10-25 20:33:17Z thierry $
+
+#
+# Usage: python retriever.py <file with URLs to fetch> [<# of
+#          concurrent connections>]
+#
+
+import sys, threading, Queue
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+# Get args
+num_conn = 10
+try:
+    if sys.argv[1] == "-":
+        urls = sys.stdin.readlines()
+    else:
+        urls = open(sys.argv[1]).readlines()
+    if len(sys.argv) >= 3:
+        num_conn = int(sys.argv[2])
+except:
+    print "Usage: %s <file with URLs to fetch> [<# of concurrent connections>]" % sys.argv[0]
+    raise SystemExit
+
+
+# Make a queue with (url, filename) tuples
+queue = Queue.Queue()
+for url in urls:
+    url = url.strip()
+    if not url or url[0] == "#":
+        continue
+    filename = "doc_%03d.dat" % (len(queue.queue) + 1)
+    queue.put((url, filename))
+
+
+# Check args
+assert queue.queue, "no URLs given"
+num_urls = len(queue.queue)
+num_conn = min(num_conn, num_urls)
+assert 1 <= num_conn <= 10000, "invalid number of concurrent connections"
+print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
+print "----- Getting", num_urls, "URLs using", num_conn, "connections -----"
+
+
+class WorkerThread(threading.Thread):
+    def __init__(self, queue):
+        threading.Thread.__init__(self)
+        self.queue = queue
+
+    def run(self):
+        while 1:
+            try:
+                url, filename = self.queue.get_nowait()
+            except Queue.Empty:
+                raise SystemExit
+            fp = open(filename, "wb")
+            curl = pycurl.Curl()
+            curl.setopt(pycurl.URL, url)
+            curl.setopt(pycurl.FOLLOWLOCATION, 1)
+            curl.setopt(pycurl.MAXREDIRS, 5)
+            curl.setopt(pycurl.CONNECTTIMEOUT, 30)
+            curl.setopt(pycurl.TIMEOUT, 300)
+            curl.setopt(pycurl.NOSIGNAL, 1)
+            curl.setopt(pycurl.WRITEDATA, fp)
+            try:
+                curl.perform()
+            except:
+                import traceback
+                traceback.print_exc(file=sys.stderr)
+                sys.stderr.flush()
+            curl.close()
+            fp.close()
+            sys.stdout.write(".")
+            sys.stdout.flush()
+
+
+# Start a bunch of threads
+threads = []
+for dummy in range(num_conn):
+    t = WorkerThread(queue)
+    t.start()
+    threads.append(t)
+
+
+# Wait for all threads to finish
+for thread in threads:
+    thread.join()
diff --git a/pycurl/examples/sfquery.py b/pycurl/examples/sfquery.py
new file mode 100644 (file)
index 0000000..0c63f61
--- /dev/null
@@ -0,0 +1,64 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+#
+# sfquery -- Source Forge query script using the ClientCGI high-level interface
+#
+# Retrieves a SourceForge XML export object for a given project.
+# Specify the *numeric* project ID. the user name, and the password,
+# as arguments. If you have a valid ~/.netrc entry for sourceforge.net,
+# you can just give the project ID.
+#
+# By Eric S. Raymond, August 2002.  All rites reversed.
+
+import os, sys, netrc
+import curl
+
+assert sys.version[:3] >= "2.2", "requires Python 2.2 or better"
+
+class SourceForgeUserSession(curl.Curl):
+    # SourceForge-specific methods.  Sensitive to changes in site design.
+    def login(self, name, password):
+        "Establish a login session."
+        self.post("account/login.php", (("form_loginname", name),
+                                        ("form_pw", password),
+                                        ("return_to", ""),
+                                        ("stay_in_ssl", "1"),
+                                        ("login", "Login With SSL")))
+    def logout(self):
+        "Log out of SourceForge."
+        self.get("account/logout.php")
+    def fetch_xml(self, numid):
+        self.get("export/xml_export.php?group_id=%s" % numid)
+
+if __name__ == "__main__":
+    if len(sys.argv) == 1:
+        project_id = '28236'    # PyCurl project ID
+    else:
+        project_id = sys.argv[1]
+    # Try to grab authenticators out of your .netrc
+    try:
+        auth = netrc.netrc().authenticators("sourceforge.net")
+        name, account, password = auth
+    except:
+        name = sys.argv[2]
+        password = sys.argv[3]
+    session = SourceForgeUserSession("https://sourceforge.net/")
+    session.set_verbosity(0)
+    session.login(name, password)
+    # Login could fail.
+    if session.answered("Invalid Password or User Name"):
+        sys.stderr.write("Login/password not accepted (%d bytes)\n" % len(session.body()))
+        sys.exit(1)
+    # We'll see this if we get the right thing.
+    elif session.answered("Personal Page For: " + name):
+        session.fetch_xml(project_id)
+        sys.stdout.write(session.body())
+        session.logout()
+        sys.exit(0)
+    # Or maybe SourceForge has changed its site design so our check strings
+    # are no longer valid.
+    else:
+        sys.stderr.write("Unexpected page (%d bytes)\n"%len(session.body()))
+        sys.exit(1)
+
diff --git a/pycurl/examples/xmlrpc_curl.py b/pycurl/examples/xmlrpc_curl.py
new file mode 100644 (file)
index 0000000..28950d8
--- /dev/null
@@ -0,0 +1,61 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: xmlrpc_curl.py 5574 2007-10-25 20:33:17Z thierry $
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import xmlrpclib, pycurl
+
+
+class CURLTransport(xmlrpclib.Transport):
+    """Handles a cURL HTTP transaction to an XML-RPC server."""
+
+    xmlrpc_h = [ "Content-Type: text/xml" ]
+
+    def __init__(self, username=None, password=None):
+        self.c = pycurl.Curl()
+        self.c.setopt(pycurl.POST, 1)
+        self.c.setopt(pycurl.NOSIGNAL, 1)
+        self.c.setopt(pycurl.CONNECTTIMEOUT, 30)
+        self.c.setopt(pycurl.HTTPHEADER, self.xmlrpc_h)
+        if username != None and password != None:
+            self.c.setopt(pycurl.USERPWD, '%s:%s' % (username, password))
+
+    def request(self, host, handler, request_body, verbose=0):
+        b = StringIO()
+        self.c.setopt(pycurl.URL, 'http://%s%s' % (host, handler))
+        self.c.setopt(pycurl.POSTFIELDS, request_body)
+        self.c.setopt(pycurl.WRITEFUNCTION, b.write)
+        self.c.setopt(pycurl.VERBOSE, verbose)
+        self.verbose = verbose
+        try:
+           self.c.perform()
+        except pycurl.error, v:
+            raise xmlrpclib.ProtocolError(
+                host + handler,
+                v[0], v[1], None
+                )
+        b.seek(0)
+        return self.parse_response(b)
+
+
+if __name__ == "__main__":
+    ## Test
+    server = xmlrpclib.ServerProxy("http://betty.userland.com",
+                                   transport=CURLTransport())
+    print server
+    try:
+        print server.examples.getStateName(41)
+    except xmlrpclib.Error, v:
+        print "ERROR", v
diff --git a/pycurl/python/curl/__init__.py b/pycurl/python/curl/__init__.py
new file mode 100644 (file)
index 0000000..8fecb4d
--- /dev/null
@@ -0,0 +1,146 @@
+# A high-level interface to the pycurl extension
+#
+# ** mfx NOTE: the CGI class uses "black magic" using COOKIEFILE in
+#    combination with a non-existant file name. See the libcurl docs
+#    for more info.
+#
+# If you want thread-safe operation, you'll have to set the NOSIGNAL option
+# yourself.
+#
+# By Eric S. Raymond, April 2003.
+
+import os, sys, urllib, exceptions, mimetools, pycurl
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+class Curl:
+    "High-level interface to cURL functions."
+    def __init__(self, base_url="", fakeheaders=[]):
+        self.handle = pycurl.Curl()
+        # These members might be set.
+        self.set_url(base_url)
+        self.verbosity = 0
+        self.fakeheaders = fakeheaders
+        # Nothing past here should be modified by the caller.
+        self.payload = ""
+        self.header = StringIO()
+        # Verify that we've got the right site; harmless on a non-SSL connect.
+        self.set_option(pycurl.SSL_VERIFYHOST, 2)
+        # Follow redirects in case it wants to take us to a CGI...
+        self.set_option(pycurl.FOLLOWLOCATION, 1)
+        self.set_option(pycurl.MAXREDIRS, 5)
+        # Setting this option with even a nonexistent file makes libcurl
+        # handle cookie capture and playback automatically.
+        self.set_option(pycurl.COOKIEFILE, "/dev/null")
+        # Set timeouts to avoid hanging too long
+        self.set_timeout(30)
+        # Use password identification from .netrc automatically
+        self.set_option(pycurl.NETRC, 1)
+        # Set up a callback to capture the payload
+        def payload_callback(x):
+            self.payload += x
+        self.set_option(pycurl.WRITEFUNCTION, payload_callback)
+        def header_callback(x):
+            self.header.write(x)
+        self.set_option(pycurl.HEADERFUNCTION, header_callback)
+
+    def set_timeout(self, timeout):
+        "Set timeout for connect and object retrieval (applies for both)"
+        self.set_option(pycurl.CONNECTTIMEOUT, timeout)
+        self.set_option(pycurl.TIMEOUT, timeout)
+
+    def set_url(self, url):
+        "Set the base URL to be retrieved."
+        self.base_url = url
+        self.set_option(pycurl.URL, self.base_url)
+
+    def set_option(self, *args):
+        "Set an option on the retrieval,"
+        apply(self.handle.setopt, args)
+
+    def set_verbosity(self, level):
+        "Set verbosity to 1 to see transactions."
+        self.set_option(pycurl.VERBOSE, level)
+
+    def __request(self, relative_url=None):
+        "Perform the pending request."
+        if self.fakeheaders:
+            self.set_option(pycurl.HTTPHEADER, self.fakeheaders)
+        if relative_url:
+            self.set_option(pycurl.URL,os.path.join(self.base_url,relative_url))
+        self.header.seek(0,0)
+        self.payload = ""
+        self.handle.perform()
+        return self.payload
+
+    def get(self, url="", params=None):
+        "Ship a GET request for a specified URL, capture the response."
+        if params:
+            url += "?" + urllib.urlencode(params)
+        self.set_option(pycurl.HTTPGET, 1)
+        return self.__request(url)
+
+    def post(self, cgi, params):
+        "Ship a POST request to a specified CGI, capture the response."
+        self.set_option(pycurl.POST, 1)
+        self.set_option(pycurl.POSTFIELDS, urllib.urlencode(params))
+        return self.__request(cgi)
+
+    def body(self):
+        "Return the body from the last response."
+        return self.payload
+
+    def info(self):
+        "Return an RFC822 object with info on the page."
+        self.header.seek(0,0)
+        url = self.handle.getinfo(pycurl.EFFECTIVE_URL)
+        if url[:5] == 'http:':
+            self.header.readline()
+            m = mimetools.Message(self.header)
+        else:
+            m = mimetools.Message(StringIO())
+        m['effective-url'] = url
+        m['http-code'] = str(self.handle.getinfo(pycurl.HTTP_CODE))
+        m['total-time'] = str(self.handle.getinfo(pycurl.TOTAL_TIME))
+        m['namelookup-time'] = str(self.handle.getinfo(pycurl.NAMELOOKUP_TIME))
+        m['connect-time'] = str(self.handle.getinfo(pycurl.CONNECT_TIME))
+        m['pretransfer-time'] = str(self.handle.getinfo(pycurl.PRETRANSFER_TIME))
+        m['redirect-time'] = str(self.handle.getinfo(pycurl.REDIRECT_TIME))
+        m['redirect-count'] = str(self.handle.getinfo(pycurl.REDIRECT_COUNT))
+        m['size-upload'] = str(self.handle.getinfo(pycurl.SIZE_UPLOAD))
+        m['size-download'] = str(self.handle.getinfo(pycurl.SIZE_DOWNLOAD))
+        m['speed-upload'] = str(self.handle.getinfo(pycurl.SPEED_UPLOAD))
+        m['header-size'] = str(self.handle.getinfo(pycurl.HEADER_SIZE))
+        m['request-size'] = str(self.handle.getinfo(pycurl.REQUEST_SIZE))
+        m['content-length-download'] = str(self.handle.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD))
+        m['content-length-upload'] = str(self.handle.getinfo(pycurl.CONTENT_LENGTH_UPLOAD))
+        m['content-type'] = (self.handle.getinfo(pycurl.CONTENT_TYPE) or '').strip(';')
+        return m
+
+    def answered(self, check):
+        "Did a given check string occur in the last payload?"
+        return self.payload.find(check) >= 0
+
+    def close(self):
+        "Close a session, freeing resources."
+        self.handle.close()
+        self.header.close()
+
+    def __del__(self):
+        self.close()
+
+
+if __name__ == "__main__":
+    if len(sys.argv) < 2:
+        url = 'http://curl.haxx.se'
+    else:
+        url = sys.argv[1]
+    c = Curl()
+    c.get(url)
+    print c.body()
+    print '='*74 + '\n'
+    print c.info()
+    c.close()
diff --git a/pycurl/setup.py b/pycurl/setup.py
new file mode 100644 (file)
index 0000000..8c7b68b
--- /dev/null
@@ -0,0 +1,199 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: setup.py 5574 2007-10-25 20:33:17Z thierry $
+
+"""Setup script for the PycURL module distribution."""
+
+PACKAGE = "pycurl"
+PY_PACKAGE = "curl"
+VERSION = "7.13.1"
+
+import glob, os, re, sys, string
+import distutils
+from distutils.core import setup
+from distutils.extension import Extension
+from distutils.util import split_quoted
+from distutils.version import LooseVersion
+
+include_dirs = []
+define_macros = []
+library_dirs = []
+libraries = []
+runtime_library_dirs = []
+extra_objects = []
+extra_compile_args = []
+extra_link_args = []
+
+
+def scan_argv(s, default):
+    p = default
+    i = 1
+    while i < len(sys.argv):
+        arg = sys.argv[i]
+        if string.find(arg, s) == 0:
+            p = arg[len(s):]
+            assert p, arg
+            del sys.argv[i]
+        else:
+            i = i + 1
+    ##print sys.argv
+    return p
+
+
+# append contents of an environment variable to library_dirs[]
+def add_libdirs(envvar, sep, fatal=0):
+    v = os.environ.get(envvar)
+    if not v:
+        return
+    for dir in string.split(v, sep):
+        dir = string.strip(dir)
+        if not dir:
+            continue
+        dir = os.path.normpath(dir)
+        if os.path.isdir(dir):
+            if not dir in library_dirs:
+                library_dirs.append(dir)
+        elif fatal:
+            print "FATAL: bad directory %s in environment variable %s" % (dir, envvar)
+            sys.exit(1)
+
+
+if sys.platform == "win32":
+    # Windows users have to configure the CURL_DIR path parameter to match
+    # their cURL source installation.  The path set here is just an example
+    # and thus unlikely to match your installation.
+    CURL_DIR = r"c:\src\build\pycurl\curl-7.13.1"
+    CURL_DIR = scan_argv("--curl-dir=", CURL_DIR)
+    print "Using curl directory:", CURL_DIR
+    assert os.path.isdir(CURL_DIR), "please check CURL_DIR in setup.py"
+    include_dirs.append(os.path.join(CURL_DIR, "include"))
+    extra_objects.append(os.path.join(CURL_DIR, "lib", "libcurl.lib"))
+    extra_link_args.extend(["gdi32.lib", "winmm.lib", "ws2_32.lib",])
+    add_libdirs("LIB", ";")
+    if string.find(sys.version, "MSC") >= 0:
+        extra_compile_args.append("-O2")
+        extra_compile_args.append("-GF")        # enable read-only string pooling
+        extra_compile_args.append("-WX")        # treat warnings as errors
+        extra_link_args.append("/opt:nowin98")  # use small section alignment
+else:
+    # Find out the rest the hard way
+    CURL_CONFIG = "curl-config"
+    CURL_CONFIG = scan_argv("--curl-config=", CURL_CONFIG)
+    d = os.popen("'%s' --version" % CURL_CONFIG).read()
+    if d:
+        d = string.strip(d)
+    if not d:
+        raise Exception, ("`%s' not found -- please install the libcurl development files" % CURL_CONFIG)
+    print "Using %s (%s)" % (CURL_CONFIG, d)
+    for e in split_quoted(os.popen("'%s' --cflags" % CURL_CONFIG).read()):
+        if e[:2] == "-I":
+            # do not add /usr/include
+            if not re.search(r"^\/+usr\/+include\/*$", e[2:]):
+                include_dirs.append(e[2:])
+        else:
+            extra_compile_args.append(e)
+    for e in split_quoted(os.popen("'%s' --libs" % CURL_CONFIG).read()):
+        if e[:2] == "-l":
+            libraries.append(e[2:])
+        elif e[:2] == "-L":
+            library_dirs.append(e[2:])
+        else:
+            extra_link_args.append(e)
+    if not libraries:
+        libraries.append("curl")
+    # Add extra compile flag for MacOS X
+    if sys.platform[:-1] == "darwin":
+        extra_link_args.append("-flat_namespace")
+
+
+###############################################################################
+
+def get_kw(**kw): return kw
+
+ext = Extension(
+    name=PACKAGE,
+    sources=[
+        os.path.join("src", "pycurl.c"),
+    ],
+    include_dirs=include_dirs,
+    define_macros=define_macros,
+    library_dirs=library_dirs,
+    libraries=libraries,
+    runtime_library_dirs=runtime_library_dirs,
+    extra_objects=extra_objects,
+    extra_compile_args=extra_compile_args,
+    extra_link_args=extra_link_args,
+)
+##print ext.__dict__; sys.exit(1)
+
+
+###############################################################################
+
+# prepare data_files
+
+def get_data_files():
+    # a list of tuples with (path to install to, a list of local files)
+    data_files = []
+    if sys.platform == "win32":
+        datadir = os.path.join("doc", PACKAGE)
+    else:
+        datadir = os.path.join("share", "doc", PACKAGE)
+    #
+    files = ["ChangeLog", "COPYING", "INSTALL", "README", "TODO",]
+    if files:
+        data_files.append((os.path.join(datadir), files))
+    files = glob.glob(os.path.join("doc", "*.html"))
+    if files:
+        data_files.append((os.path.join(datadir, "html"), files))
+    files = glob.glob(os.path.join("examples", "*.py"))
+    if files:
+        data_files.append((os.path.join(datadir, "examples"), files))
+    files = glob.glob(os.path.join("tests", "*.py"))
+    if files:
+        data_files.append((os.path.join(datadir, "tests"), files))
+    #
+    assert data_files
+    for install_dir, files in data_files:
+        assert files
+        for f in files:
+            assert os.path.isfile(f), (f, install_dir)
+    return data_files
+
+##print get_data_files(); sys.exit(1)
+
+
+###############################################################################
+
+setup_args = get_kw(
+    name=PACKAGE,
+    version=VERSION,
+    description="PycURL -- cURL library module for Python",
+    author="Kjetil Jacobsen, Markus F.X.J. Oberhumer",
+    author_email="kjetilja@cs.uit.no, markus@oberhumer.com",
+    maintainer="Kjetil Jacobsen, Markus F.X.J. Oberhumer",
+    maintainer_email="kjetilja@cs.uit.no, markus@oberhumer.com",
+    url="http://pycurl.sourceforge.net/",
+    license="GNU Lesser General Public License (LGPL)",
+    data_files=get_data_files(),
+    ext_modules=[ext],
+    long_description="""
+This module provides Python bindings for the cURL library.""",
+)
+
+if sys.version >= "2.2":
+    setup_args["packages"] = [PY_PACKAGE]
+    setup_args["package_dir"] = { PY_PACKAGE: os.path.join('python', 'curl') }
+
+
+##print distutils.__version__
+if LooseVersion(distutils.__version__) > LooseVersion("1.0.1"):
+    setup_args["platforms"] = "All"
+if LooseVersion(distutils.__version__) < LooseVersion("1.0.3"):
+    setup_args["licence"] = setup_args["license"]
+
+if __name__ == "__main__":
+    for o in ext.extra_objects:
+        assert os.path.isfile(o), o
+    # We can live with the deprecationwarning for a while
+    apply(setup, (), setup_args)
diff --git a/pycurl/setup_win32_ssl.py b/pycurl/setup_win32_ssl.py
new file mode 100644 (file)
index 0000000..96072a8
--- /dev/null
@@ -0,0 +1,34 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: setup_win32_ssl.py 5574 2007-10-25 20:33:17Z thierry $
+
+import os, sys, string
+assert sys.platform == "win32", "Only for building on Win32 with SSL and zlib"
+
+
+CURL_DIR = r"c:\src\build\pycurl\curl-7.13.1-ssl"
+OPENSSL_DIR = r"c:\src\build\pycurl\openssl-0.9.7e"
+sys.argv.insert(1, "--curl-dir=" + CURL_DIR)
+
+from setup import *
+
+setup_args["name"] = "pycurl-ssl"
+
+
+for l in ("libeay32.lib", "ssleay32.lib",):
+    ext.extra_objects.append(os.path.join(OPENSSL_DIR, "out32", l))
+
+pool = "\\" + r"pool\win32\vc6" + "\\"
+if string.find(sys.version, "MSC v.1310") >= 0:
+    pool = "\\" + r"pool\win32\vc71" + "\\"
+ext.extra_objects.append(r"c:\src\pool\zlib-1.2.2" + pool + "zlib.lib")
+ext.extra_objects.append(r"c:\src\pool\c-ares-20041212" + pool + "ares.lib")
+ext.extra_objects.append(r"c:\src\pool\libidn-0.5.13" + pool + "idn.lib")
+
+
+if __name__ == "__main__":
+    for o in ext.extra_objects:
+        assert os.path.isfile(o), o
+    apply(setup, (), setup_args)
+
diff --git a/pycurl/src/Makefile b/pycurl/src/Makefile
new file mode 100644 (file)
index 0000000..a4828d3
--- /dev/null
@@ -0,0 +1,19 @@
+CC=gcc
+RM=rm
+CP=cp
+PYINCLUDE=/usr/include/python2.2
+CURLINCLUDE=/usr/include/curl
+INCLUDE=-I$(PYINCLUDE) -I$(CURLINCLUDE)
+LIBS=-L/usr/lib -lcurl
+LDOPTS=-shared
+CCOPTS=-g -O2 -Wall -Wstrict-prototypes -fPIC
+
+all:
+       $(CC) $(INCLUDE) $(CCOPTS) -c pycurl.c -o pycurl.o
+       $(CC) $(LIBS) $(LDOPTS) -lcurl pycurl.o -o pycurl.so
+
+install: all
+       $(CP) pycurl.so /usr/lib/python2.2/site-packages
+
+clean:
+       $(RM) -f *~ *.o *obj *.so
diff --git a/pycurl/src/pycurl.c b/pycurl/src/pycurl.c
new file mode 100644 (file)
index 0000000..e7a3139
--- /dev/null
@@ -0,0 +1,2828 @@
+/* $Id: pycurl.c 5574 2007-10-25 20:33:17Z thierry $ */
+
+/* PycURL -- cURL Python module
+ *
+ * Authors:
+ *  Copyright (C) 2001-2005 by Kjetil Jacobsen <kjetilja at cs.uit.no>
+ *  Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer <markus at oberhumer.com>
+ *
+ * Contributions:
+ *  Tino Lange <Tino.Lange at gmx.de>
+ *  Matt King <matt at gnik.com>
+ *  Conrad Steenberg <conrad at hep.caltech.edu>
+ *  Amit Mongia <amit_mongia at hotmail.com>
+ *  Eric S. Raymond <esr at thyrsus.com>
+ *  Martin Muenstermann <mamuema at sourceforge.net>
+ *  Domenico Andreoli <cavok at libero.it>
+ *
+ * See file COPYING for license information.
+ *
+ * Some quick info on Python's refcount:
+ *   Py_BuildValue          does incref the item(s)
+ *   PyArg_ParseTuple       does NOT incref the item
+ *   PyList_Append          does incref the item
+ *   PyTuple_SET_ITEM       does NOT incref the item
+ *   PyTuple_SetItem        does NOT incref the item
+ *   PyXXX_GetItem          returns a borrowed reference
+ */
+
+#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
+#  define WIN32 1
+#endif
+#if defined(WIN32)
+#  define CURL_STATICLIB 1
+#endif
+#include <Python.h>
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <curl/curl.h>
+#include <curl/multi.h>
+#undef NDEBUG
+#include <assert.h>
+
+/* Ensure we have updated versions */
+#if !defined(PY_VERSION_HEX) || (PY_VERSION_HEX < 0x02020000)
+#  error "Need Python version 2.2 or greater to compile pycurl."
+#endif
+#if !defined(LIBCURL_VERSION_NUM) || (LIBCURL_VERSION_NUM < 0x070d01)
+#  error "Need libcurl version 7.13.1 or greater to compile pycurl."
+#endif
+
+#undef UNUSED
+#define UNUSED(var)     ((void)&var)
+
+#undef COMPILE_TIME_ASSERT
+#define COMPILE_TIME_ASSERT(expr) \
+     { typedef int compile_time_assert_fail__[1 - 2 * !(expr)]; }
+
+
+/* Calculate the number of OBJECTPOINT options we need to store */
+#define OPTIONS_SIZE    ((int)CURLOPT_LASTENTRY % 10000)
+static int OPT_INDEX(int o)
+{
+    assert(o >= CURLOPTTYPE_OBJECTPOINT);
+    assert(o < CURLOPTTYPE_OBJECTPOINT + OPTIONS_SIZE);
+    return o - CURLOPTTYPE_OBJECTPOINT;
+}
+
+
+static PyObject *ErrorObject = NULL;
+static PyTypeObject *p_Curl_Type = NULL;
+static PyTypeObject *p_CurlMulti_Type = NULL;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *dict;                 /* Python attributes dictionary */
+    CURLM *multi_handle;
+    PyThreadState *state;
+    fd_set read_fd_set;
+    fd_set write_fd_set;
+    fd_set exc_fd_set;
+} CurlMultiObject;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject *dict;                 /* Python attributes dictionary */
+    CURL *handle;
+    PyThreadState *state;
+    CurlMultiObject *multi_stack;
+    struct curl_httppost *httppost;
+    struct curl_slist *httpheader;
+    struct curl_slist *http200aliases;
+    struct curl_slist *quote;
+    struct curl_slist *postquote;
+    struct curl_slist *prequote;
+    struct curl_slist *source_prequote;
+    struct curl_slist *source_postquote;
+    /* callbacks */
+    PyObject *w_cb;
+    PyObject *h_cb;
+    PyObject *r_cb;
+    PyObject *pro_cb;
+    PyObject *debug_cb;
+    PyObject *ioctl_cb;
+    /* file objects */
+    PyObject *readdata_fp;
+    PyObject *writedata_fp;
+    PyObject *writeheader_fp;
+    /* misc */
+    void *options[OPTIONS_SIZE];    /* for OBJECTPOINT options */
+    char error[CURL_ERROR_SIZE+1];
+} CurlObject;
+
+/* Throw exception based on return value `res' and `self->error' */
+#define CURLERROR_RETVAL() do {\
+    PyObject *v; \
+    self->error[sizeof(self->error) - 1] = 0; \
+    v = Py_BuildValue("(is)", (int) (res), self->error); \
+    if (v != NULL) { PyErr_SetObject(ErrorObject, v); Py_DECREF(v); } \
+    return NULL; \
+} while (0)
+
+/* Throw exception based on return value `res' and custom message */
+#define CURLERROR_MSG(msg) do {\
+    PyObject *v; const char *m = (msg); \
+    v = Py_BuildValue("(is)", (int) (res), (m)); \
+    if (v != NULL) { PyErr_SetObject(ErrorObject, v); Py_DECREF(v); } \
+    return NULL; \
+} while (0)
+
+
+/* Safe XDECREF for object states that handles nested deallocations */
+#define ZAP(v) do {\
+    PyObject *tmp = (PyObject *)(v); \
+    (v) = NULL; \
+    Py_XDECREF(tmp); \
+} while (0)
+
+
+/*************************************************************************
+// python utility functions
+**************************************************************************/
+
+#if (PY_VERSION_HEX < 0x02030000) && !defined(PY_LONG_LONG)
+#  define PY_LONG_LONG LONG_LONG
+#endif
+
+/* Like PyString_AsString(), but set an exception if the string contains
+ * embedded NULs. Actually PyString_AsStringAndSize() already does that for
+ * us if the `len' parameter is NULL - see Objects/stringobject.c.
+ */
+
+static char *PyString_AsString_NoNUL(PyObject *obj)
+{
+    char *s = NULL;
+    int r;
+    r = PyString_AsStringAndSize(obj, &s, NULL);
+    if (r != 0)
+        return NULL;    /* exception already set */
+    assert(s != NULL);
+    return s;
+}
+
+
+/* Convert a curl slist (a list of strings) to a Python list.
+ * In case of error return NULL with an exception set.
+ */
+static PyObject *convert_slist(struct curl_slist *slist, int free_flags)
+{
+    PyObject *ret = NULL;
+
+    ret = PyList_New(0);
+    if (ret == NULL) goto error;
+
+    for ( ; slist != NULL; slist = slist->next) {
+        PyObject *v = NULL;
+
+        if (slist->data != NULL) {
+            v = PyString_FromString(slist->data);
+            if (v == NULL || PyList_Append(ret, v) != 0) {
+                Py_XDECREF(v);
+                goto error;
+            }
+            Py_DECREF(v);
+        }
+    }
+
+    if ((free_flags & 1) && slist)
+        curl_slist_free_all(slist);
+    return ret;
+
+error:
+    Py_XDECREF(ret);
+    if ((free_flags & 2) && slist)
+        curl_slist_free_all(slist);
+    return NULL;
+}
+
+
+/*************************************************************************
+// static utility functions
+**************************************************************************/
+
+static PyThreadState *
+get_thread_state(const CurlObject *self)
+{
+    /* Get the thread state for callbacks to run in.
+     * This is either `self->state' when running inside perform() or
+     * `self->multi_stack->state' when running inside multi_perform().
+     * When the result is != NULL we also implicitly assert
+     * a valid `self->handle'.
+     */
+    if (self == NULL)
+        return NULL;
+    assert(self->ob_type == p_Curl_Type);
+    if (self->state != NULL)
+    {
+        /* inside perform() */
+        assert(self->handle != NULL);
+        if (self->multi_stack != NULL) {
+            assert(self->multi_stack->state == NULL);
+        }
+        return self->state;
+    }
+    if (self->multi_stack != NULL && self->multi_stack->state != NULL)
+    {
+        /* inside multi_perform() */
+        assert(self->handle != NULL);
+        assert(self->multi_stack->multi_handle != NULL);
+        assert(self->state == NULL);
+        return self->multi_stack->state;
+    }
+    return NULL;
+}
+
+
+/* assert some CurlObject invariants */
+static void
+assert_curl_state(const CurlObject *self)
+{
+    assert(self != NULL);
+    assert(self->ob_type == p_Curl_Type);
+    (void) get_thread_state(self);
+}
+
+
+/* assert some CurlMultiObject invariants */
+static void
+assert_multi_state(const CurlMultiObject *self)
+{
+    assert(self != NULL);
+    assert(self->ob_type == p_CurlMulti_Type);
+    if (self->state != NULL) {
+        assert(self->multi_handle != NULL);
+    }
+}
+
+
+/* check state for methods */
+static int
+check_curl_state(const CurlObject *self, int flags, const char *name)
+{
+    assert_curl_state(self);
+    if ((flags & 1) && self->handle == NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - no curl handle", name);
+        return -1;
+    }
+    if ((flags & 2) && get_thread_state(self) != NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - perform() is currently running", name);
+        return -1;
+    }
+    return 0;
+}
+
+static int
+check_multi_state(const CurlMultiObject *self, int flags, const char *name)
+{
+    assert_multi_state(self);
+    if ((flags & 1) && self->multi_handle == NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - no multi handle", name);
+        return -1;
+    }
+    if ((flags & 2) && self->state != NULL) {
+        PyErr_Format(ErrorObject, "cannot invoke %s() - multi_perform() is currently running", name);
+        return -1;
+    }
+    return 0;
+}
+
+
+/*************************************************************************
+// CurlObject
+**************************************************************************/
+
+/* --------------- construct/destruct (i.e. open/close) --------------- */
+
+/* Allocate a new python curl object */
+static CurlObject *
+util_curl_new(void)
+{
+    CurlObject *self;
+
+    self = (CurlObject *) PyObject_GC_New(CurlObject, p_Curl_Type);
+    if (self == NULL)
+        return NULL;
+    PyObject_GC_Track(self);
+
+    /* Set python curl object initial values */
+    self->dict = NULL;
+    self->handle = NULL;
+    self->state = NULL;
+    self->multi_stack = NULL;
+    self->httppost = NULL;
+    self->httpheader = NULL;
+    self->http200aliases = NULL;
+    self->quote = NULL;
+    self->postquote = NULL;
+    self->prequote = NULL;
+    self->source_postquote = NULL;
+    self->source_prequote = NULL;
+
+    /* Set callback pointers to NULL by default */
+    self->w_cb = NULL;
+    self->h_cb = NULL;
+    self->r_cb = NULL;
+    self->pro_cb = NULL;
+    self->debug_cb = NULL;
+    self->ioctl_cb = NULL;
+
+    /* Set file object pointers to NULL by default */
+    self->readdata_fp = NULL;
+    self->writedata_fp = NULL;
+    self->writeheader_fp = NULL;
+
+    /* Zero string pointer memory buffer used by setopt */
+    memset(self->options, 0, sizeof(self->options));
+    memset(self->error, 0, sizeof(self->error));
+
+    return self;
+}
+
+
+/* constructor - this is a module-level function returning a new instance */
+static CurlObject *
+do_curl_new(PyObject *dummy)
+{
+    CurlObject *self = NULL;
+    int res;
+    char *s = NULL;
+
+    UNUSED(dummy);
+
+    /* Allocate python curl object */
+    self = util_curl_new();
+    if (self == NULL)
+        return NULL;
+
+    /* Initialize curl handle */
+    self->handle = curl_easy_init();
+    if (self->handle == NULL)
+        goto error;
+
+    /* Set curl error buffer and zero it */
+    res = curl_easy_setopt(self->handle, CURLOPT_ERRORBUFFER, self->error);
+    if (res != CURLE_OK)
+        goto error;
+    memset(self->error, 0, sizeof(self->error));
+
+    /* Set backreference */
+    res = curl_easy_setopt(self->handle, CURLOPT_PRIVATE, (char *) self);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Enable NOPROGRESS by default, i.e. no progress output */
+    res = curl_easy_setopt(self->handle, CURLOPT_NOPROGRESS, (long)1);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Disable VERBOSE by default, i.e. no verbose output */
+    res = curl_easy_setopt(self->handle, CURLOPT_VERBOSE, (long)0);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Set FTP_ACCOUNT to NULL by default */
+    res = curl_easy_setopt(self->handle, CURLOPT_FTP_ACCOUNT, NULL);
+    if (res != CURLE_OK)
+        goto error;
+
+    /* Set default USERAGENT */
+    s = (char *) malloc(7 + strlen(LIBCURL_VERSION) + 1);
+    if (s == NULL)
+        goto error;
+    strcpy(s, "PycURL/"); strcpy(s+7, LIBCURL_VERSION);
+    res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) s);
+    if (res != CURLE_OK) {
+        free(s);
+        goto error;
+    }
+    self->options[ OPT_INDEX(CURLOPT_USERAGENT) ] = s; s = NULL;
+
+    /* Success - return new object */
+    return self;
+
+error:
+    Py_DECREF(self);    /* this also closes self->handle */
+    PyErr_SetString(ErrorObject, "initializing curl failed");
+    return NULL;
+}
+
+
+/* util function shared by close() and clear() */
+static void
+util_curl_xdecref(CurlObject *self, int flags, CURL *handle)
+{
+    if (flags & 1) {
+        /* Decrement refcount for attributes dictionary. */
+        ZAP(self->dict);
+    }
+
+    if (flags & 2) {
+        /* Decrement refcount for multi_stack. */
+        if (self->multi_stack != NULL) {
+            CurlMultiObject *multi_stack = self->multi_stack;
+            self->multi_stack = NULL;
+            if (multi_stack->multi_handle != NULL && handle != NULL) {
+                (void) curl_multi_remove_handle(multi_stack->multi_handle, handle);
+            }
+            Py_DECREF(multi_stack);
+        }
+    }
+
+    if (flags & 4) {
+        /* Decrement refcount for python callbacks. */
+        ZAP(self->w_cb);
+        ZAP(self->h_cb);
+        ZAP(self->r_cb);
+        ZAP(self->pro_cb);
+        ZAP(self->debug_cb);
+        ZAP(self->ioctl_cb);
+    }
+
+    if (flags & 8) {
+        /* Decrement refcount for python file objects. */
+        ZAP(self->readdata_fp);
+        ZAP(self->writedata_fp);
+        ZAP(self->writeheader_fp);
+    }
+}
+
+
+static void
+util_curl_close(CurlObject *self)
+{
+    CURL *handle;
+    int i;
+
+    /* Zero handle and thread-state to disallow any operations to be run
+     * from now on */
+    assert(self != NULL);
+    assert(self->ob_type == p_Curl_Type);
+    handle = self->handle;
+    self->handle = NULL;
+    if (handle == NULL) {
+        /* Some paranoia assertions just to make sure the object
+         * deallocation problem is finally really fixed... */
+        assert(self->state == NULL);
+        assert(self->multi_stack == NULL);
+        return;             /* already closed */
+    }
+    self->state = NULL;
+
+    /* Decref multi stuff which uses this handle */
+    util_curl_xdecref(self, 2, handle);
+
+    /* Cleanup curl handle - must be done without the gil */
+    Py_BEGIN_ALLOW_THREADS
+    curl_easy_cleanup(handle);
+    Py_END_ALLOW_THREADS
+    handle = NULL;
+
+    /* Decref callbacks and file handles */
+    util_curl_xdecref(self, 4 | 8, handle);
+
+    /* Free all variables allocated by setopt */
+#undef SFREE
+#define SFREE(v)   if ((v) != NULL) (curl_formfree(v), (v) = NULL)
+    SFREE(self->httppost);
+#undef SFREE
+#define SFREE(v)   if ((v) != NULL) (curl_slist_free_all(v), (v) = NULL)
+    SFREE(self->httpheader);
+    SFREE(self->http200aliases);
+    SFREE(self->quote);
+    SFREE(self->postquote);
+    SFREE(self->prequote);
+    SFREE(self->source_postquote);
+    SFREE(self->source_prequote);
+#undef SFREE
+
+    /* Last, free the options.  This must be done after the curl handle
+     * is closed since libcurl assumes that some options are valid when
+     * invoking curl_easy_cleanup(). */
+    for (i = 0; i < OPTIONS_SIZE; i++) {
+        if (self->options[i] != NULL) {
+            free(self->options[i]);
+            self->options[i] = NULL;
+        }
+    }
+}
+
+
+static void
+do_curl_dealloc(CurlObject *self)
+{
+    PyObject_GC_UnTrack(self);
+    Py_TRASHCAN_SAFE_BEGIN(self)
+
+    ZAP(self->dict);
+    util_curl_close(self);
+
+    PyObject_GC_Del(self);
+    Py_TRASHCAN_SAFE_END(self)
+}
+
+
+static PyObject *
+do_curl_close(CurlObject *self)
+{
+    if (check_curl_state(self, 2, "close") != 0) {
+        return NULL;
+    }
+    util_curl_close(self);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+static PyObject *
+do_curl_errstr(CurlObject *self)
+{
+    if (check_curl_state(self, 1 | 2, "errstr") != 0) {
+        return NULL;
+    }
+    self->error[sizeof(self->error) - 1] = 0;
+    return PyString_FromString(self->error);
+}
+
+
+/* --------------- GC support --------------- */
+
+/* Drop references that may have created reference cycles. */
+static int
+do_curl_clear(CurlObject *self)
+{
+    assert(get_thread_state(self) == NULL);
+    util_curl_xdecref(self, 1 | 2 | 4 | 8, self->handle);
+    return 0;
+}
+
+/* Traverse all refcounted objects. */
+static int
+do_curl_traverse(CurlObject *self, visitproc visit, void *arg)
+{
+    int err;
+#undef VISIT
+#define VISIT(v)    if ((v) != NULL && ((err = visit(v, arg)) != 0)) return err
+
+    VISIT(self->dict);
+    VISIT((PyObject *) self->multi_stack);
+
+    VISIT(self->w_cb);
+    VISIT(self->h_cb);
+    VISIT(self->r_cb);
+    VISIT(self->pro_cb);
+    VISIT(self->debug_cb);
+    VISIT(self->ioctl_cb);
+
+    VISIT(self->readdata_fp);
+    VISIT(self->writedata_fp);
+    VISIT(self->writeheader_fp);
+
+    return 0;
+#undef VISIT
+}
+
+
+/* --------------- perform --------------- */
+
+static PyObject *
+do_curl_perform(CurlObject *self)
+{
+    int res;
+
+    if (check_curl_state(self, 1 | 2, "perform") != 0) {
+        return NULL;
+    }
+
+    /* Save handle to current thread (used as context for python callbacks) */
+    self->state = PyThreadState_Get();
+    assert(self->state != NULL);
+
+    /* Release global lock and start */
+    Py_BEGIN_ALLOW_THREADS
+    res = curl_easy_perform(self->handle);
+    Py_END_ALLOW_THREADS
+
+    /* Zero thread-state to disallow callbacks to be run from now on */
+    self->state = NULL;
+
+    if (res != CURLE_OK) {
+        CURLERROR_RETVAL();
+    }
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* --------------- callback handlers --------------- */
+
+/* IMPORTANT NOTE: due to threading issues, we cannot call _any_ Python
+ * function without acquiring the thread state in the callback handlers.
+ */
+
+static size_t
+util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    size_t ret = 0;     /* assume error */
+    PyObject *cb;
+    int total_size;
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    cb = flags ? self->h_cb : self->w_cb;
+    if (cb == NULL)
+        goto silent_error;
+    if (size <= 0 || nmemb <= 0)
+        goto done;
+    total_size = (int)(size * nmemb);
+    if (total_size < 0 || (size_t)total_size / size != nmemb) {
+        PyErr_SetString(ErrorObject, "integer overflow in write callback");
+        goto verbose_error;
+    }
+
+    /* run callback */
+    arglist = Py_BuildValue("(s#)", ptr, total_size);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (result == Py_None) {
+        ret = total_size;           /* None means success */
+    }
+    else if (PyInt_Check(result)) {
+        long obj_size = PyInt_AsLong(result);
+        if (obj_size < 0 || obj_size > total_size) {
+            PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size);
+            goto verbose_error;
+        }
+        ret = (size_t) obj_size;    /* success */
+    }
+    else if (PyLong_Check(result)) {
+        long obj_size = PyLong_AsLong(result);
+        if (obj_size < 0 || obj_size > total_size) {
+            PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size);
+            goto verbose_error;
+        }
+        ret = (size_t) obj_size;    /* success */
+    }
+    else {
+        PyErr_SetString(ErrorObject, "write callback must return int or None");
+        goto verbose_error;
+    }
+
+done:
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static size_t
+write_callback(char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    return util_write_callback(0, ptr, size, nmemb, stream);
+}
+
+static size_t
+header_callback(char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    return util_write_callback(1, ptr, size, nmemb, stream);
+}
+
+
+static size_t
+read_callback(char *ptr, size_t size, size_t nmemb, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+
+    size_t ret = CURL_READFUNC_ABORT;     /* assume error, this actually works */
+    int total_size;
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->r_cb == NULL)
+        goto silent_error;
+    if (size <= 0 || nmemb <= 0)
+        goto done;
+    total_size = (int)(size * nmemb);
+    if (total_size < 0 || (size_t)total_size / size != nmemb) {
+        PyErr_SetString(ErrorObject, "integer overflow in read callback");
+        goto verbose_error;
+    }
+
+    /* run callback */
+    arglist = Py_BuildValue("(i)", total_size);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->r_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (PyString_Check(result)) {
+        char *buf = NULL;
+        int obj_size = -1;
+        int r;
+        r = PyString_AsStringAndSize(result, &buf, &obj_size);
+        if (r != 0 || obj_size < 0 || obj_size > total_size) {
+            PyErr_Format(ErrorObject, "invalid return value for read callback %ld %ld", (long)obj_size, (long)total_size);
+            goto verbose_error;
+        }
+        memcpy(ptr, buf, obj_size);
+        ret = obj_size;             /* success */
+    }
+    else if (PyInt_Check(result)) {
+        long r = PyInt_AsLong(result);
+        if (r != CURL_READFUNC_ABORT) {
+            goto type_error;
+        }
+        /* ret is CURL_READUNC_ABORT */
+    }
+    else if (PyLong_Check(result)) {
+        long r = PyLong_AsLong(result);
+        if (r != CURL_READFUNC_ABORT) {
+            goto type_error;
+        }
+        /* ret is CURL_READUNC_ABORT */
+    }
+    else {
+    type_error:
+        PyErr_SetString(ErrorObject, "read callback must return string");
+        goto verbose_error;
+    }
+
+done:
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static int
+progress_callback(void *stream,
+                  double dltotal, double dlnow, double ultotal, double ulnow)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    int ret = 1;       /* assume error */
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->pro_cb == NULL)
+        goto silent_error;
+
+    /* run callback */
+    arglist = Py_BuildValue("(dddd)", dltotal, dlnow, ultotal, ulnow);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->pro_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (result == Py_None) {
+        ret = 0;        /* None means success */
+    }
+    else if (PyInt_Check(result)) {
+        ret = (int) PyInt_AsLong(result);
+    }
+    else {
+        ret = PyObject_IsTrue(result);  /* FIXME ??? */
+    }
+
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static int
+debug_callback(CURL *curlobj, curl_infotype type,
+               char *buffer, size_t total_size, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    int ret = 0;       /* always success */
+
+    UNUSED(curlobj);
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->debug_cb == NULL)
+        goto silent_error;
+    if ((int)total_size < 0 || (size_t)((int)total_size) != total_size) {
+        PyErr_SetString(ErrorObject, "integer overflow in debug callback");
+        goto verbose_error;
+    }
+
+    /* run callback */
+    arglist = Py_BuildValue("(is#)", (int)type, buffer, (int)total_size);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->debug_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* return values from debug callbacks should be ignored */
+
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+static curlioerr
+ioctl_callback(CURL *curlobj, int cmd, void *stream)
+{
+    CurlObject *self;
+    PyThreadState *tmp_state;
+    PyObject *arglist;
+    PyObject *result = NULL;
+    int ret = CURLIOE_FAILRESTART;       /* assume error */
+
+    UNUSED(curlobj);
+
+    /* acquire thread */
+    self = (CurlObject *)stream;
+    tmp_state = get_thread_state(self);
+    if (tmp_state == NULL)
+        return (curlioerr) ret;
+    PyEval_AcquireThread(tmp_state);
+
+    /* check args */
+    if (self->ioctl_cb == NULL)
+        goto silent_error;
+
+    /* run callback */
+    arglist = Py_BuildValue("(i)", (int)cmd);
+    if (arglist == NULL)
+        goto verbose_error;
+    result = PyEval_CallObject(self->ioctl_cb, arglist);
+    Py_DECREF(arglist);
+    if (result == NULL)
+        goto verbose_error;
+
+    /* handle result */
+    if (result == Py_None) {
+        ret = CURLIOE_OK;        /* None means success */
+    }
+    else if (PyInt_Check(result)) {
+        ret = (int) PyInt_AsLong(result);
+        if (ret >= CURLIOE_LAST || ret < 0) {
+            PyErr_SetString(ErrorObject, "ioctl callback returned invalid value");
+            goto verbose_error;
+        }
+    }
+
+silent_error:
+    Py_XDECREF(result);
+    PyEval_ReleaseThread(tmp_state);
+    return (curlioerr) ret;
+verbose_error:
+    PyErr_Print();
+    goto silent_error;
+}
+
+
+/* --------------- unsetopt/setopt/getinfo --------------- */
+
+static PyObject *
+util_curl_unsetopt(CurlObject *self, int option)
+{
+    int res;
+    int opt_index = -1;
+
+#define SETOPT2(o,x) \
+    if ((res = curl_easy_setopt(self->handle, (o), (x))) != CURLE_OK) goto error
+#define SETOPT(x)   SETOPT2((CURLoption)option, (x))
+
+    /* FIXME: implement more options. Have to carefully check lib/url.c in the
+     *   libcurl source code to see if it's actually safe to simply
+     *   unset the option. */
+    switch (option)
+    {
+    case CURLOPT_HTTPPOST:
+        SETOPT((void *) 0);
+        curl_formfree(self->httppost);
+        self->httppost = NULL;
+        /* FIXME: what about data->set.httpreq ?? */
+        break;
+    case CURLOPT_INFILESIZE:
+        SETOPT((long) -1);
+        break;
+    case CURLOPT_WRITEHEADER:
+        SETOPT((void *) 0);
+        ZAP(self->writeheader_fp);
+        break;
+    case CURLOPT_CAINFO:
+    case CURLOPT_CAPATH:
+    case CURLOPT_COOKIE:
+    case CURLOPT_COOKIEJAR:
+    case CURLOPT_CUSTOMREQUEST:
+    case CURLOPT_EGDSOCKET:
+    case CURLOPT_FTPPORT:
+    case CURLOPT_PROXYUSERPWD:
+    case CURLOPT_RANDOM_FILE:
+    case CURLOPT_SSL_CIPHER_LIST:
+    case CURLOPT_USERPWD:
+        SETOPT((char *) 0);
+        opt_index = OPT_INDEX(option);
+        break;
+
+    /* info: we explicitly list unsupported options here */
+    case CURLOPT_COOKIEFILE:
+    default:
+        PyErr_SetString(PyExc_TypeError, "unsetopt() is not supported for this option");
+        return NULL;
+    }
+
+    if (opt_index >= 0 && self->options[opt_index] != NULL) {
+        free(self->options[opt_index]);
+        self->options[opt_index] = NULL;
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+
+error:
+    CURLERROR_RETVAL();
+
+#undef SETOPT
+#undef SETOPT2
+}
+
+
+static PyObject *
+do_curl_unsetopt(CurlObject *self, PyObject *args)
+{
+    int option;
+
+    if (!PyArg_ParseTuple(args, "i:unsetopt", &option)) {
+        return NULL;
+    }
+    if (check_curl_state(self, 1 | 2, "unsetopt") != 0) {
+        return NULL;
+    }
+
+    /* early checks of option value */
+    if (option <= 0)
+        goto error;
+    if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE)
+        goto error;
+    if (option % 10000 >= OPTIONS_SIZE)
+        goto error;
+
+    return util_curl_unsetopt(self, option);
+
+error:
+    PyErr_SetString(PyExc_TypeError, "invalid arguments to unsetopt");
+    return NULL;
+}
+
+
+static PyObject *
+do_curl_setopt(CurlObject *self, PyObject *args)
+{
+    int option;
+    PyObject *obj;
+    int res;
+
+    if (!PyArg_ParseTuple(args, "iO:setopt", &option, &obj))
+        return NULL;
+    if (check_curl_state(self, 1 | 2, "setopt") != 0)
+        return NULL;
+
+    /* early checks of option value */
+    if (option <= 0)
+        goto error;
+    if (option >= (int)CURLOPTTYPE_OFF_T + OPTIONS_SIZE)
+        goto error;
+    if (option % 10000 >= OPTIONS_SIZE)
+        goto error;
+
+#if 0 /* XXX - should we ??? */
+    /* Handle the case of None */
+    if (obj == Py_None) {
+        return util_curl_unsetopt(self, option);
+    }
+#endif
+
+    /* Handle the case of string arguments */
+    if (PyString_Check(obj)) {
+        char *str = NULL;
+        int len = -1;
+        char *buf;
+        int opt_index;
+
+        /* Check that the option specified a string as well as the input */
+        switch (option) {
+        case CURLOPT_CAINFO:
+        case CURLOPT_CAPATH:
+        case CURLOPT_COOKIE:
+        case CURLOPT_COOKIEFILE:
+        case CURLOPT_COOKIEJAR:
+        case CURLOPT_CUSTOMREQUEST:
+        case CURLOPT_EGDSOCKET:
+        case CURLOPT_ENCODING:
+        case CURLOPT_FTPPORT:
+        case CURLOPT_INTERFACE:
+        case CURLOPT_KRB4LEVEL:
+        case CURLOPT_NETRC_FILE:
+        case CURLOPT_PROXY:
+        case CURLOPT_PROXYUSERPWD:
+        case CURLOPT_RANDOM_FILE:
+        case CURLOPT_RANGE:
+        case CURLOPT_REFERER:
+        case CURLOPT_SSLCERT:
+        case CURLOPT_SSLCERTTYPE:
+        case CURLOPT_SSLENGINE:
+        case CURLOPT_SSLKEY:
+        case CURLOPT_SSLKEYPASSWD:
+        case CURLOPT_SSLKEYTYPE:
+        case CURLOPT_SSL_CIPHER_LIST:
+        case CURLOPT_URL:
+        case CURLOPT_USERAGENT:
+        case CURLOPT_USERPWD:
+        case CURLOPT_SOURCE_HOST:
+        case CURLOPT_SOURCE_USERPWD:
+        case CURLOPT_SOURCE_PATH:
+/* FIXME: check if more of these options allow binary data */
+            str = PyString_AsString_NoNUL(obj);
+            if (str == NULL)
+                return NULL;
+            break;
+        case CURLOPT_POSTFIELDS:
+            if (PyString_AsStringAndSize(obj, &str, &len) != 0)
+                return NULL;
+            /* automatically set POSTFIELDSIZE */
+            res = curl_easy_setopt(self->handle, CURLOPT_POSTFIELDSIZE, (long)len);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            break;
+        default:
+            PyErr_SetString(PyExc_TypeError, "strings are not supported for this option");
+            return NULL;
+        }
+        /* Allocate memory to hold the string */
+        assert(str != NULL);
+        if (len <= 0)
+            buf = strdup(str);
+        else {
+            buf = (char *) malloc(len);
+            if (buf) memcpy(buf, str, len);
+        }
+        if (buf == NULL)
+            return PyErr_NoMemory();
+        /* Call setopt */
+        res = curl_easy_setopt(self->handle, (CURLoption)option, buf);
+        /* Check for errors */
+        if (res != CURLE_OK) {
+            free(buf);
+            CURLERROR_RETVAL();
+        }
+        /* Save allocated option buffer */
+        opt_index = OPT_INDEX(option);
+        if (self->options[opt_index] != NULL) {
+            free(self->options[opt_index]);
+            self->options[opt_index] = NULL;
+        }
+        self->options[opt_index] = buf;
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+#define IS_LONG_OPTION(o)   (o < CURLOPTTYPE_OBJECTPOINT)
+#define IS_OFF_T_OPTION(o)  (o >= CURLOPTTYPE_OFF_T)
+
+    /* Handle the case of integer arguments */
+    if (PyInt_Check(obj)) {
+        long d = PyInt_AsLong(obj);
+
+        if (IS_LONG_OPTION(option))
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d);
+        else if (IS_OFF_T_OPTION(option))
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)d);
+        else {
+            PyErr_SetString(PyExc_TypeError, "integers are not supported for this option");
+            return NULL;
+        }
+        if (res != CURLE_OK) {
+            CURLERROR_RETVAL();
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Handle the case of long arguments (used by *_LARGE options) */
+    if (PyLong_Check(obj)) {
+        PY_LONG_LONG d = PyLong_AsLongLong(obj);
+        if (d == -1 && PyErr_Occurred())
+            return NULL;
+
+        if (IS_LONG_OPTION(option) && (long)d == d)
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (long)d);
+        else if (IS_OFF_T_OPTION(option) && (curl_off_t)d == d)
+            res = curl_easy_setopt(self->handle, (CURLoption)option, (curl_off_t)d);
+        else {
+            PyErr_SetString(PyExc_TypeError, "longs are not supported for this option");
+            return NULL;
+        }
+        if (res != CURLE_OK) {
+            CURLERROR_RETVAL();
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+#undef IS_LONG_OPTION
+#undef IS_OFF_T_OPTION
+
+    /* Handle the case of file objects */
+    if (PyFile_Check(obj)) {
+        FILE *fp;
+
+        /* Ensure the option specified a file as well as the input */
+        switch (option) {
+        case CURLOPT_READDATA:
+        case CURLOPT_WRITEDATA:
+            break;
+        case CURLOPT_WRITEHEADER:
+            if (self->w_cb != NULL) {
+                PyErr_SetString(ErrorObject, "cannot combine WRITEHEADER with WRITEFUNCTION.");
+                return NULL;
+            }
+            break;
+        default:
+            PyErr_SetString(PyExc_TypeError, "files are not supported for this option");
+            return NULL;
+        }
+
+        fp = PyFile_AsFile(obj);
+        if (fp == NULL) {
+            PyErr_SetString(PyExc_TypeError, "second argument must be open file");
+            return NULL;
+        }
+        res = curl_easy_setopt(self->handle, (CURLoption)option, fp);
+        if (res != CURLE_OK) {
+            CURLERROR_RETVAL();
+        }
+        Py_INCREF(obj);
+
+        switch (option) {
+        case CURLOPT_READDATA:
+            ZAP(self->readdata_fp);
+            self->readdata_fp = obj;
+            break;
+        case CURLOPT_WRITEDATA:
+            ZAP(self->writedata_fp);
+            self->writedata_fp = obj;
+            break;
+        case CURLOPT_WRITEHEADER:
+            ZAP(self->writeheader_fp);
+            self->writeheader_fp = obj;
+            break;
+        default:
+            assert(0);
+            break;
+        }
+        /* Return success */
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Handle the case of list objects */
+    if (PyList_Check(obj)) {
+        struct curl_slist **old_slist = NULL;
+        struct curl_slist *slist = NULL;
+        int i, len;
+
+        switch (option) {
+        case CURLOPT_HTTP200ALIASES:
+            old_slist = &self->http200aliases;
+            break;
+        case CURLOPT_HTTPHEADER:
+            old_slist = &self->httpheader;
+            break;
+        case CURLOPT_QUOTE:
+            old_slist = &self->quote;
+            break;
+        case CURLOPT_POSTQUOTE:
+            old_slist = &self->postquote;
+            break;
+        case CURLOPT_PREQUOTE:
+            old_slist = &self->prequote;
+            break;
+        case CURLOPT_SOURCE_PREQUOTE:
+            old_slist = &self->source_prequote;
+            break;
+        case CURLOPT_SOURCE_POSTQUOTE:
+            old_slist = &self->source_postquote;
+            break;
+        case CURLOPT_HTTPPOST:
+            break;
+        default:
+            /* None of the list options were recognized, throw exception */
+            PyErr_SetString(PyExc_TypeError, "lists are not supported for this option");
+            return NULL;
+        }
+
+        len = PyList_Size(obj);
+        if (len == 0) {
+            /* Empty list - do nothing */
+            Py_INCREF(Py_None);
+            return Py_None;
+        }
+
+        /* Handle HTTPPOST different since we construct a HttpPost form struct */
+        if (option == CURLOPT_HTTPPOST) {
+            struct curl_httppost *post = NULL;
+            struct curl_httppost *last = NULL;
+
+            for (i = 0; i < len; i++) {
+                char *nstr = NULL, *cstr = NULL;
+                int nlen = -1, clen = -1;
+                PyObject *listitem = PyList_GetItem(obj, i);
+
+                if (!PyTuple_Check(listitem)) {
+                    curl_formfree(post);
+                    PyErr_SetString(PyExc_TypeError, "list items must be tuple objects");
+                    return NULL;
+                }
+                if (PyTuple_GET_SIZE(listitem) != 2) {
+                    curl_formfree(post);
+                    PyErr_SetString(PyExc_TypeError, "tuple must contain two elements (name, value)");
+                    return NULL;
+                }
+                if (PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 0), &nstr, &nlen) != 0) {
+                    curl_formfree(post);
+                    PyErr_SetString(PyExc_TypeError, "tuple must contain string as first element");
+                    return NULL;
+                }
+                if (PyString_Check(PyTuple_GET_ITEM(listitem, 1))) {
+                    /* Handle strings as second argument for backwards compatibility */
+                    PyString_AsStringAndSize(PyTuple_GET_ITEM(listitem, 1), &cstr, &clen);
+                    /* INFO: curl_formadd() internally does memdup() the data, so
+                     * embedded NUL characters _are_ allowed here. */
+                    res = curl_formadd(&post, &last,
+                                       CURLFORM_COPYNAME, nstr,
+                                       CURLFORM_NAMELENGTH, (long) nlen,
+                                       CURLFORM_COPYCONTENTS, cstr,
+                                       CURLFORM_CONTENTSLENGTH, (long) clen,
+                                       CURLFORM_END);
+                    if (res != CURLE_OK) {
+                        curl_formfree(post);
+                        CURLERROR_RETVAL();
+                    }
+                }
+                else if (PyTuple_Check(PyTuple_GET_ITEM(listitem, 1))) {
+                    /* Supports content, file and content-type */
+                    PyObject *t = PyTuple_GET_ITEM(listitem, 1);
+                    int tlen = PyTuple_Size(t);
+                    int j, k, l;
+                    struct curl_forms *forms = NULL;
+
+                    /* Sanity check that there are at least two tuple items */
+                    if (tlen < 2) {
+                        curl_formfree(post);
+                        PyErr_SetString(PyExc_TypeError, "tuple must contain at least one option and one value");
+                        return NULL;
+                    }
+
+                    /* Allocate enough space to accommodate length options for content */
+                    forms = PyMem_Malloc(sizeof(struct curl_forms) * ((tlen*2) + 1));
+                    if (forms == NULL) {
+                        curl_formfree(post);
+                        PyErr_NoMemory();
+                        return NULL;
+                    }
+
+                    /* Iterate all the tuple members pairwise */
+                    for (j = 0, k = 0, l = 0; j < tlen; j += 2, l++) {
+                        char *ostr;
+                        int olen, val;
+
+                        if (j == (tlen-1)) {
+                            PyErr_SetString(PyExc_TypeError, "expected value");
+                            PyMem_Free(forms);
+                            curl_formfree(post);
+                            return NULL;
+                        }
+                        if (!PyInt_Check(PyTuple_GET_ITEM(t, j))) {
+                            PyErr_SetString(PyExc_TypeError, "option must be long");
+                            PyMem_Free(forms);
+                            curl_formfree(post);
+                            return NULL;
+                        }
+                        if (!PyString_Check(PyTuple_GET_ITEM(t, j+1))) {
+                            PyErr_SetString(PyExc_TypeError, "value must be string");
+                            PyMem_Free(forms);
+                            curl_formfree(post);
+                            return NULL;
+                        }
+
+                        val = PyLong_AsLong(PyTuple_GET_ITEM(t, j));
+                        if (val != CURLFORM_COPYCONTENTS &&
+                            val != CURLFORM_FILE &&
+                            val != CURLFORM_CONTENTTYPE)
+                        {
+                            PyErr_SetString(PyExc_TypeError, "unsupported option");
+                            PyMem_Free(forms);
+                            curl_formfree(post);
+                            return NULL;
+                        }
+                        PyString_AsStringAndSize(PyTuple_GET_ITEM(t, j+1), &ostr, &olen);
+                        forms[k].option = val;
+                        forms[k].value = ostr;
+                        ++k;
+                        if (val == CURLFORM_COPYCONTENTS) {
+                            /* Contents can contain \0 bytes so we specify the length */
+                            forms[k].option = CURLFORM_CONTENTSLENGTH;
+                            forms[k].value = (char *)olen;
+                            ++k;
+                        }
+                    }
+                    forms[k].option = CURLFORM_END;
+                    res = curl_formadd(&post, &last,
+                                       CURLFORM_COPYNAME, nstr,
+                                       CURLFORM_NAMELENGTH, (long) nlen,
+                                       CURLFORM_ARRAY, forms,
+                                       CURLFORM_END);
+                    PyMem_Free(forms);
+                    if (res != CURLE_OK) {
+                        curl_formfree(post);
+                        CURLERROR_RETVAL();
+                    }
+                } else {
+                    /* Some other type was given, ignore */
+                    curl_formfree(post);
+                    PyErr_SetString(PyExc_TypeError, "unsupported second type in tuple");
+                    return NULL;
+                }
+            }
+            res = curl_easy_setopt(self->handle, CURLOPT_HTTPPOST, post);
+            /* Check for errors */
+            if (res != CURLE_OK) {
+                curl_formfree(post);
+                CURLERROR_RETVAL();
+            }
+            /* Finally, free previously allocated httppost and update */
+            curl_formfree(self->httppost);
+            self->httppost = post;
+
+            Py_INCREF(Py_None);
+            return Py_None;
+        }
+
+        /* Just to be sure we do not bug off here */
+        assert(old_slist != NULL && slist == NULL);
+
+        /* Handle regular list operations on the other options */
+        for (i = 0; i < len; i++) {
+            PyObject *listitem = PyList_GetItem(obj, i);
+            struct curl_slist *nlist;
+            char *str;
+
+            if (!PyString_Check(listitem)) {
+                curl_slist_free_all(slist);
+                PyErr_SetString(PyExc_TypeError, "list items must be string objects");
+                return NULL;
+            }
+            /* INFO: curl_slist_append() internally does strdup() the data, so
+             * no embedded NUL characters allowed here. */
+            str = PyString_AsString_NoNUL(listitem);
+            if (str == NULL) {
+                curl_slist_free_all(slist);
+                return NULL;
+            }
+            nlist = curl_slist_append(slist, str);
+            if (nlist == NULL || nlist->data == NULL) {
+                curl_slist_free_all(slist);
+                return PyErr_NoMemory();
+            }
+            slist = nlist;
+        }
+        res = curl_easy_setopt(self->handle, (CURLoption)option, slist);
+        /* Check for errors */
+        if (res != CURLE_OK) {
+            curl_slist_free_all(slist);
+            CURLERROR_RETVAL();
+        }
+        /* Finally, free previously allocated list and update */
+        curl_slist_free_all(*old_slist);
+        *old_slist = slist;
+
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Handle the case of function objects for callbacks */
+    if (PyFunction_Check(obj) || PyCFunction_Check(obj) || PyMethod_Check(obj)) {
+        /* We use function types here to make sure that our callback
+         * definitions exactly match the <curl/curl.h> interface.
+         */
+        const curl_write_callback w_cb = write_callback;
+        const curl_write_callback h_cb = header_callback;
+        const curl_read_callback r_cb = read_callback;
+        const curl_progress_callback pro_cb = progress_callback;
+        const curl_debug_callback debug_cb = debug_callback;
+        const curl_ioctl_callback ioctl_cb = ioctl_callback;
+
+        switch(option) {
+        case CURLOPT_WRITEFUNCTION:
+            if (self->writeheader_fp != NULL) {
+                PyErr_SetString(ErrorObject, "cannot combine WRITEFUNCTION with WRITEHEADER option.");
+                return NULL;
+            }
+            Py_INCREF(obj);
+            ZAP(self->writedata_fp);
+            ZAP(self->w_cb);
+            self->w_cb = obj;
+            curl_easy_setopt(self->handle, CURLOPT_WRITEFUNCTION, w_cb);
+            curl_easy_setopt(self->handle, CURLOPT_WRITEDATA, self);
+            break;
+        case CURLOPT_HEADERFUNCTION:
+            Py_INCREF(obj);
+            ZAP(self->h_cb);
+            self->h_cb = obj;
+            curl_easy_setopt(self->handle, CURLOPT_HEADERFUNCTION, h_cb);
+            curl_easy_setopt(self->handle, CURLOPT_WRITEHEADER, self);
+            break;
+        case CURLOPT_READFUNCTION:
+            Py_INCREF(obj);
+            ZAP(self->readdata_fp);
+            ZAP(self->r_cb);
+            self->r_cb = obj;
+            curl_easy_setopt(self->handle, CURLOPT_READFUNCTION, r_cb);
+            curl_easy_setopt(self->handle, CURLOPT_READDATA, self);
+            break;
+        case CURLOPT_PROGRESSFUNCTION:
+            Py_INCREF(obj);
+            ZAP(self->pro_cb);
+            self->pro_cb = obj;
+            curl_easy_setopt(self->handle, CURLOPT_PROGRESSFUNCTION, pro_cb);
+            curl_easy_setopt(self->handle, CURLOPT_PROGRESSDATA, self);
+            break;
+        case CURLOPT_DEBUGFUNCTION:
+            Py_INCREF(obj);
+            ZAP(self->debug_cb);
+            self->debug_cb = obj;
+            curl_easy_setopt(self->handle, CURLOPT_DEBUGFUNCTION, debug_cb);
+            curl_easy_setopt(self->handle, CURLOPT_DEBUGDATA, self);
+            break;
+        case CURLOPT_IOCTLFUNCTION:
+            Py_INCREF(obj);
+            ZAP(self->ioctl_cb);
+            self->ioctl_cb = obj;
+            curl_easy_setopt(self->handle, CURLOPT_IOCTLFUNCTION, ioctl_cb);
+            curl_easy_setopt(self->handle, CURLOPT_IOCTLDATA, self);
+            break;
+
+        default:
+            /* None of the function options were recognized, throw exception */
+            PyErr_SetString(PyExc_TypeError, "functions are not supported for this option");
+            return NULL;
+        }
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    /* Failed to match any of the function signatures -- return error */
+error:
+    PyErr_SetString(PyExc_TypeError, "invalid arguments to setopt");
+    return NULL;
+}
+
+
+static PyObject *
+do_curl_getinfo(CurlObject *self, PyObject *args)
+{
+    int option;
+    int res;
+
+    if (!PyArg_ParseTuple(args, "i:getinfo", &option)) {
+        return NULL;
+    }
+    if (check_curl_state(self, 1 | 2, "getinfo") != 0) {
+        return NULL;
+    }
+
+    switch (option) {
+    case CURLINFO_FILETIME:
+    case CURLINFO_HEADER_SIZE:
+    case CURLINFO_HTTP_CODE:
+    case CURLINFO_REDIRECT_COUNT:
+    case CURLINFO_REQUEST_SIZE:
+    case CURLINFO_SSL_VERIFYRESULT:
+    case CURLINFO_HTTP_CONNECTCODE:
+    case CURLINFO_HTTPAUTH_AVAIL:
+    case CURLINFO_PROXYAUTH_AVAIL:
+    case CURLINFO_OS_ERRNO:
+    case CURLINFO_NUM_CONNECTS:
+        {
+            /* Return PyInt as result */
+            long l_res = -1;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &l_res);
+            /* Check for errors and return result */
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            return PyInt_FromLong(l_res);
+        }
+
+    case CURLINFO_CONTENT_TYPE:
+    case CURLINFO_EFFECTIVE_URL:
+        {
+            /* Return PyString as result */
+            char *s_res = NULL;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &s_res);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            /* If the resulting string is NULL, return None */
+            if (s_res == NULL) {
+                Py_INCREF(Py_None);
+                return Py_None;
+            }
+            return PyString_FromString(s_res);
+        }
+
+    case CURLINFO_CONNECT_TIME:
+    case CURLINFO_CONTENT_LENGTH_DOWNLOAD:
+    case CURLINFO_CONTENT_LENGTH_UPLOAD:
+    case CURLINFO_NAMELOOKUP_TIME:
+    case CURLINFO_PRETRANSFER_TIME:
+    case CURLINFO_REDIRECT_TIME:
+    case CURLINFO_SIZE_DOWNLOAD:
+    case CURLINFO_SIZE_UPLOAD:
+    case CURLINFO_SPEED_DOWNLOAD:
+    case CURLINFO_SPEED_UPLOAD:
+    case CURLINFO_STARTTRANSFER_TIME:
+    case CURLINFO_TOTAL_TIME:
+        {
+            /* Return PyFloat as result */
+            double d_res = 0.0;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &d_res);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            return PyFloat_FromDouble(d_res);
+        }
+
+    case CURLINFO_SSL_ENGINES:
+        {
+            /* Return a list of strings */
+            struct curl_slist *slist = NULL;
+
+            res = curl_easy_getinfo(self->handle, (CURLINFO)option, &slist);
+            if (res != CURLE_OK) {
+                CURLERROR_RETVAL();
+            }
+            return convert_slist(slist, 1 | 2);
+        }
+    }
+
+    /* Got wrong option on the method call */
+    PyErr_SetString(PyExc_ValueError, "invalid argument to getinfo");
+    return NULL;
+}
+
+
+/*************************************************************************
+// CurlMultiObject
+**************************************************************************/
+
+/* --------------- construct/destruct (i.e. open/close) --------------- */
+
+/* constructor - this is a module-level function returning a new instance */
+static CurlMultiObject *
+do_multi_new(PyObject *dummy)
+{
+    CurlMultiObject *self;
+
+    UNUSED(dummy);
+
+    /* Allocate python curl-multi object */
+    self = (CurlMultiObject *) PyObject_GC_New(CurlMultiObject, p_CurlMulti_Type);
+    if (self) {
+        PyObject_GC_Track(self);
+    }
+    else {
+        return NULL;
+    }
+
+    /* Initialize object attributes */
+    self->dict = NULL;
+    self->state = NULL;
+
+    /* Allocate libcurl multi handle */
+    self->multi_handle = curl_multi_init();
+    if (self->multi_handle == NULL) {
+        Py_DECREF(self);
+        PyErr_SetString(ErrorObject, "initializing curl-multi failed");
+        return NULL;
+    }
+    return self;
+}
+
+
+static void
+util_multi_close(CurlMultiObject *self)
+{
+    assert(self != NULL);
+    self->state = NULL;
+    if (self->multi_handle != NULL) {
+        CURLM *multi_handle = self->multi_handle;
+        self->multi_handle = NULL;
+        curl_multi_cleanup(multi_handle);
+    }
+}
+
+
+static void
+do_multi_dealloc(CurlMultiObject *self)
+{
+    PyObject_GC_UnTrack(self);
+    Py_TRASHCAN_SAFE_BEGIN(self)
+
+    ZAP(self->dict);
+    util_multi_close(self);
+
+    PyObject_GC_Del(self);
+    Py_TRASHCAN_SAFE_END(self)
+}
+
+
+static PyObject *
+do_multi_close(CurlMultiObject *self)
+{
+    if (check_multi_state(self, 2, "close") != 0) {
+        return NULL;
+    }
+    util_multi_close(self);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* --------------- GC support --------------- */
+
+/* Drop references that may have created reference cycles. */
+static int
+do_multi_clear(CurlMultiObject *self)
+{
+    ZAP(self->dict);
+    return 0;
+}
+
+static int
+do_multi_traverse(CurlMultiObject *self, visitproc visit, void *arg)
+{
+    int err;
+#undef VISIT
+#define VISIT(v)    if ((v) != NULL && ((err = visit(v, arg)) != 0)) return err
+
+    VISIT(self->dict);
+
+    return 0;
+#undef VISIT
+}
+
+/* --------------- perform --------------- */
+
+
+static PyObject *
+do_multi_perform(CurlMultiObject *self)
+{
+    CURLMcode res;
+    int running = -1;
+
+    if (check_multi_state(self, 1 | 2, "perform") != 0) {
+        return NULL;
+    }
+
+    /* Release global lock and start */
+    self->state = PyThreadState_Get();
+    assert(self->state != NULL);
+    Py_BEGIN_ALLOW_THREADS
+    res = curl_multi_perform(self->multi_handle, &running);
+    Py_END_ALLOW_THREADS
+    self->state = NULL;
+
+    /* We assume these errors are ok, otherwise throw exception */
+    if (res != CURLM_OK && res != CURLM_CALL_MULTI_PERFORM) {
+        CURLERROR_MSG("perform failed");
+    }
+
+    /* Return a tuple with the result and the number of running handles */
+    return Py_BuildValue("(ii)", (int)res, running);
+}
+
+
+/* --------------- add_handle/remove_handle --------------- */
+
+/* static utility function */
+static int
+check_multi_add_remove(const CurlMultiObject *self, const CurlObject *obj)
+{
+    /* check CurlMultiObject status */
+    assert_multi_state(self);
+    if (self->multi_handle == NULL) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - multi-stack is closed");
+        return -1;
+    }
+    if (self->state != NULL) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - multi_perform() already running");
+        return -1;
+    }
+    /* check CurlObject status */
+    assert_curl_state(obj);
+    if (obj->state != NULL) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - perform() of curl object already running");
+        return -1;
+    }
+    if (obj->multi_stack != NULL && obj->multi_stack != self) {
+        PyErr_SetString(ErrorObject, "cannot add/remove handle - curl object already on another multi-stack");
+        return -1;
+    }
+    return 0;
+}
+
+
+static PyObject *
+do_multi_add_handle(CurlMultiObject *self, PyObject *args)
+{
+    CurlObject *obj;
+    CURLMcode res;
+
+    if (!PyArg_ParseTuple(args, "O!:add_handle", p_Curl_Type, &obj)) {
+        return NULL;
+    }
+    if (check_multi_add_remove(self, obj) != 0) {
+        return NULL;
+    }
+    if (obj->handle == NULL) {
+        PyErr_SetString(ErrorObject, "curl object already closed");
+        return NULL;
+    }
+    if (obj->multi_stack == self) {
+        PyErr_SetString(ErrorObject, "curl object already on this multi-stack");
+        return NULL;
+    }
+    assert(obj->multi_stack == NULL);
+    res = curl_multi_add_handle(self->multi_handle, obj->handle);
+    if (res != CURLM_OK) {
+        CURLERROR_MSG("curl_multi_add_handle() failed due to internal errors");
+    }
+    obj->multi_stack = self;
+    Py_INCREF(self);
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+static PyObject *
+do_multi_remove_handle(CurlMultiObject *self, PyObject *args)
+{
+    CurlObject *obj;
+    CURLMcode res;
+
+    if (!PyArg_ParseTuple(args, "O!:remove_handle", p_Curl_Type, &obj)) {
+        return NULL;
+    }
+    if (check_multi_add_remove(self, obj) != 0) {
+        return NULL;
+    }
+    if (obj->handle == NULL) {
+        /* CurlObject handle already closed -- ignore */
+        goto done;
+    }
+    if (obj->multi_stack != self) {
+        PyErr_SetString(ErrorObject, "curl object not on this multi-stack");
+        return NULL;
+    }
+    res = curl_multi_remove_handle(self->multi_handle, obj->handle);
+    if (res != CURLM_OK) {
+        CURLERROR_MSG("curl_multi_remove_handle() failed due to internal errors");
+    }
+    assert(obj->multi_stack == self);
+    obj->multi_stack = NULL;
+    Py_DECREF(self);
+done:
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* --------------- fdset ---------------------- */
+
+static PyObject *
+do_multi_fdset(CurlMultiObject *self)
+{
+    CURLMcode res;
+    int max_fd = -1, fd;
+    PyObject *ret = NULL;
+    PyObject *read_list = NULL, *write_list = NULL, *except_list = NULL;
+    PyObject *py_fd = NULL;
+
+    if (check_multi_state(self, 1 | 2, "fdset") != 0) {
+        return NULL;
+    }
+
+    /* Clear file descriptor sets */
+    FD_ZERO(&self->read_fd_set);
+    FD_ZERO(&self->write_fd_set);
+    FD_ZERO(&self->exc_fd_set);
+
+    /* Don't bother releasing the gil as this is just a data structure operation */
+    res = curl_multi_fdset(self->multi_handle, &self->read_fd_set,
+                           &self->write_fd_set, &self->exc_fd_set, &max_fd);
+    if (res != CURLM_OK || max_fd < 0) {
+        CURLERROR_MSG("curl_multi_fdset() failed due to internal errors");
+    }
+
+    /* Allocate lists. */
+    if ((read_list = PyList_New(0)) == NULL) goto error;
+    if ((write_list = PyList_New(0)) == NULL) goto error;
+    if ((except_list = PyList_New(0)) == NULL) goto error;
+
+    /* Populate lists */
+    for (fd = 0; fd < max_fd + 1; fd++) {
+        if (FD_ISSET(fd, &self->read_fd_set)) {
+            if ((py_fd = PyInt_FromLong((long)fd)) == NULL) goto error;
+            if (PyList_Append(read_list, py_fd) != 0) goto error;
+            Py_DECREF(py_fd);
+            py_fd = NULL;
+        }
+        if (FD_ISSET(fd, &self->write_fd_set)) {
+            if ((py_fd = PyInt_FromLong((long)fd)) == NULL) goto error;
+            if (PyList_Append(write_list, py_fd) != 0) goto error;
+            Py_DECREF(py_fd);
+            py_fd = NULL;
+        }
+        if (FD_ISSET(fd, &self->exc_fd_set)) {
+            if ((py_fd = PyInt_FromLong((long)fd)) == NULL) goto error;
+            if (PyList_Append(except_list, py_fd) != 0) goto error;
+            Py_DECREF(py_fd);
+            py_fd = NULL;
+        }
+    }
+
+    /* Return a tuple with the 3 lists */
+    ret = Py_BuildValue("(OOO)", read_list, write_list, except_list);
+error:
+    Py_XDECREF(py_fd);
+    Py_XDECREF(except_list);
+    Py_XDECREF(write_list);
+    Py_XDECREF(read_list);
+    return ret;
+}
+
+
+/* --------------- info_read --------------- */
+
+static PyObject *
+do_multi_info_read(CurlMultiObject *self, PyObject *args)
+{
+    PyObject *ret = NULL;
+    PyObject *ok_list = NULL, *err_list = NULL;
+    CURLMsg *msg;
+    int in_queue = 0, num_results = INT_MAX;
+
+    /* Sanity checks */
+    if (!PyArg_ParseTuple(args, "|i:info_read", &num_results)) {
+        return NULL;
+    }
+    if (num_results <= 0) {
+        PyErr_SetString(ErrorObject, "argument to info_read must be greater than zero");
+        return NULL;
+    }
+    if (check_multi_state(self, 1 | 2, "info_read") != 0) {
+        return NULL;
+    }
+
+    if ((ok_list = PyList_New(0)) == NULL) goto error;
+    if ((err_list = PyList_New(0)) == NULL) goto error;
+
+    /* Loop through all messages */
+    while ((msg = curl_multi_info_read(self->multi_handle, &in_queue)) != NULL) {
+        CURLcode res;
+        CurlObject *co = NULL;
+
+        /* Check for termination as specified by the user */
+        if (num_results-- <= 0) {
+            break;
+        }
+
+        /* Fetch the curl object that corresponds to the curl handle in the message */
+        res = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &co);
+        if (res != CURLE_OK || co == NULL) {
+            Py_DECREF(err_list);
+            Py_DECREF(ok_list);
+            CURLERROR_MSG("Unable to fetch curl handle from curl object");
+        }
+        assert(co->ob_type == p_Curl_Type);
+        if (msg->msg != CURLMSG_DONE) {
+            /* FIXME: what does this mean ??? */
+        }
+        if (msg->data.result == CURLE_OK) {
+            /* Append curl object to list of objects which succeeded */
+            if (PyList_Append(ok_list, (PyObject *)co) != 0) {
+                goto error;
+            }
+        }
+        else {
+            /* Create a result tuple that will get added to err_list. */
+            PyObject *v = Py_BuildValue("(Ois)", (PyObject *)co, (int)msg->data.result, co->error);
+            /* Append curl object to list of objects which failed */
+            if (v == NULL || PyList_Append(err_list, v) != 0) {
+                Py_XDECREF(v);
+                goto error;
+            }
+            Py_DECREF(v);
+        }
+    }
+    /* Return (number of queued messages, [ok_objects], [error_objects]) */
+    ret = Py_BuildValue("(iOO)", in_queue, ok_list, err_list);
+error:
+    Py_XDECREF(err_list);
+    Py_XDECREF(ok_list);
+    return ret;
+}
+
+
+/* --------------- select --------------- */
+
+static PyObject *
+do_multi_select(CurlMultiObject *self, PyObject *args)
+{
+    int max_fd = -1, n;
+    double timeout = -1.0;
+    struct timeval tv, *tvp;
+    CURLMcode res;
+
+    if (!PyArg_ParseTuple(args, "|d:select", &timeout)) {
+        return NULL;
+    }
+    if (check_multi_state(self, 1 | 2, "select") != 0) {
+        return NULL;
+    }
+
+   if (timeout == -1.0) {
+        /* no timeout given - wait forever */
+        tvp = NULL;
+   } else if (timeout < 0 || timeout >= 365 * 24 * 60 * 60) {
+        PyErr_SetString(PyExc_OverflowError, "invalid timeout period");
+        return NULL;
+   } else {
+        long seconds = (long)timeout;
+        timeout = timeout - (double)seconds;
+        assert(timeout >= 0.0); assert(timeout < 1.0);
+        tv.tv_sec = seconds;
+        tv.tv_usec = (long)(timeout*1000000.0);
+        tvp = &tv;
+    }
+
+    FD_ZERO(&self->read_fd_set);
+    FD_ZERO(&self->write_fd_set);
+    FD_ZERO(&self->exc_fd_set);
+
+    res = curl_multi_fdset(self->multi_handle, &self->read_fd_set,
+                           &self->write_fd_set, &self->exc_fd_set, &max_fd);
+    if (res != CURLM_OK) {
+        CURLERROR_MSG("multi_fdset failed");
+    }
+
+    if (max_fd < 0) {
+        n = 0;
+    }
+    else {
+        Py_BEGIN_ALLOW_THREADS
+        n = select(max_fd + 1, &self->read_fd_set, &self->write_fd_set, &self->exc_fd_set, tvp);
+        Py_END_ALLOW_THREADS
+        /* info: like Python's socketmodule.c we do not raise an exception
+         *       if select() fails - we'll leave it to the actual libcurl
+         *       socket code to report any errors.
+         */
+    }
+
+    return PyInt_FromLong(n);
+}
+
+
+/*************************************************************************
+// type definitions
+**************************************************************************/
+
+/* --------------- methods --------------- */
+
+static char co_close_doc [] = "close() -> None.  Close handle and end curl session.\n";
+static char co_errstr_doc [] = "errstr() -> String.  Return the internal libcurl error buffer string.\n";
+static char co_getinfo_doc [] = "getinfo(info) -> Res.  Extract and return information from a curl session.  Throws pycurl.error exception upon failure.\n";
+static char co_perform_doc [] = "perform() -> None.  Perform a file transfer.  Throws pycurl.error exception upon failure.\n";
+static char co_setopt_doc [] = "setopt(option, parameter) -> None.  Set curl session option.  Throws pycurl.error exception upon failure.\n";
+static char co_unsetopt_doc [] = "unsetopt(option) -> None.  Reset curl session option to default value.  Throws pycurl.error exception upon failure.\n";
+
+static char co_multi_fdset_doc [] = "fdset() -> Tuple.  Returns a tuple of three lists that can be passed to the select.select() method .\n";
+static char co_multi_info_read_doc [] = "info_read([max_objects]) -> Tuple. Returns a tuple (number of queued handles, [curl objects]).\n";
+static char co_multi_select_doc [] = "select([timeout]) -> Int.  Returns result from doing a select() on the curl multi file descriptor with the given timeout.\n";
+
+static PyMethodDef curlobject_methods[] = {
+    {"close", (PyCFunction)do_curl_close, METH_NOARGS, co_close_doc},
+    {"errstr", (PyCFunction)do_curl_errstr, METH_NOARGS, co_errstr_doc},
+    {"getinfo", (PyCFunction)do_curl_getinfo, METH_VARARGS, co_getinfo_doc},
+    {"perform", (PyCFunction)do_curl_perform, METH_NOARGS, co_perform_doc},
+    {"setopt", (PyCFunction)do_curl_setopt, METH_VARARGS, co_setopt_doc},
+    {"unsetopt", (PyCFunction)do_curl_unsetopt, METH_VARARGS, co_unsetopt_doc},
+    {NULL, NULL, 0, NULL}
+};
+
+static PyMethodDef curlmultiobject_methods[] = {
+    {"add_handle", (PyCFunction)do_multi_add_handle, METH_VARARGS, NULL},
+    {"close", (PyCFunction)do_multi_close, METH_NOARGS, NULL},
+    {"fdset", (PyCFunction)do_multi_fdset, METH_NOARGS, co_multi_fdset_doc},
+    {"info_read", (PyCFunction)do_multi_info_read, METH_VARARGS, co_multi_info_read_doc},
+    {"perform", (PyCFunction)do_multi_perform, METH_NOARGS, NULL},
+    {"remove_handle", (PyCFunction)do_multi_remove_handle, METH_VARARGS, NULL},
+    {"select", (PyCFunction)do_multi_select, METH_VARARGS, co_multi_select_doc},
+    {NULL, NULL, 0, NULL}
+};
+
+
+/* --------------- setattr/getattr --------------- */
+
+static PyObject *curlobject_constants = NULL;
+static PyObject *curlmultiobject_constants = NULL;
+
+static int
+my_setattr(PyObject **dict, char *name, PyObject *v)
+{
+    if (v == NULL) {
+        int rv = -1;
+        if (*dict != NULL)
+            rv = PyDict_DelItemString(*dict, name);
+        if (rv < 0)
+            PyErr_SetString(PyExc_AttributeError, "delete non-existing attribute");
+        return rv;
+    }
+    if (*dict == NULL) {
+        *dict = PyDict_New();
+        if (*dict == NULL)
+            return -1;
+    }
+    return PyDict_SetItemString(*dict, name, v);
+}
+
+static PyObject *
+my_getattr(PyObject *co, char *name, PyObject *dict1, PyObject *dict2, PyMethodDef *m)
+{
+    PyObject *v = NULL;
+    if (v == NULL && dict1 != NULL)
+        v = PyDict_GetItemString(dict1, name);
+    if (v == NULL && dict2 != NULL)
+        v = PyDict_GetItemString(dict2, name);
+    if (v != NULL) {
+        Py_INCREF(v);
+        return v;
+    }
+    return Py_FindMethod(m, co, name);
+}
+
+static int
+do_curl_setattr(CurlObject *co, char *name, PyObject *v)
+{
+    assert_curl_state(co);
+    return my_setattr(&co->dict, name, v);
+}
+
+static int
+do_multi_setattr(CurlMultiObject *co, char *name, PyObject *v)
+{
+    assert_multi_state(co);
+    return my_setattr(&co->dict, name, v);
+}
+
+static PyObject *
+do_curl_getattr(CurlObject *co, char *name)
+{
+    assert_curl_state(co);
+    return my_getattr((PyObject *)co, name, co->dict,
+                      curlobject_constants, curlobject_methods);
+}
+
+static PyObject *
+do_multi_getattr(CurlMultiObject *co, char *name)
+{
+    assert_multi_state(co);
+    return my_getattr((PyObject *)co, name, co->dict,
+                      curlmultiobject_constants, curlmultiobject_methods);
+}
+
+
+/* --------------- actual type definitions --------------- */
+
+static PyTypeObject Curl_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                          /* ob_size */
+    "pycurl.Curl",              /* tp_name */
+    sizeof(CurlObject),         /* tp_basicsize */
+    0,                          /* tp_itemsize */
+    /* Methods */
+    (destructor)do_curl_dealloc,   /* tp_dealloc */
+    0,                          /* tp_print */
+    (getattrfunc)do_curl_getattr,  /* tp_getattr */
+    (setattrfunc)do_curl_setattr,  /* tp_setattr */
+    0,                          /* tp_compare */
+    0,                          /* tp_repr */
+    0,                          /* tp_as_number */
+    0,                          /* tp_as_sequence */
+    0,                          /* tp_as_mapping */
+    0,                          /* tp_hash */
+    0,                          /* tp_call */
+    0,                          /* tp_str */
+    0,                          /* tp_getattro */
+    0,                          /* tp_setattro */
+    0,                          /* tp_as_buffer */
+    Py_TPFLAGS_HAVE_GC,         /* tp_flags */
+    0,                          /* tp_doc */
+    (traverseproc)do_curl_traverse, /* tp_traverse */
+    (inquiry)do_curl_clear      /* tp_clear */
+    /* More fields follow here, depending on your Python version. You can
+     * safely ignore any compiler warnings about missing initializers.
+     */
+};
+
+static PyTypeObject CurlMulti_Type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                          /* ob_size */
+    "pycurl.CurlMulti",         /* tp_name */
+    sizeof(CurlMultiObject),    /* tp_basicsize */
+    0,                          /* tp_itemsize */
+    /* Methods */
+    (destructor)do_multi_dealloc,   /* tp_dealloc */
+    0,                          /* tp_print */
+    (getattrfunc)do_multi_getattr,  /* tp_getattr */
+    (setattrfunc)do_multi_setattr,  /* tp_setattr */
+    0,                          /* tp_compare */
+    0,                          /* tp_repr */
+    0,                          /* tp_as_number */
+    0,                          /* tp_as_sequence */
+    0,                          /* tp_as_mapping */
+    0,                          /* tp_hash */
+    0,                          /* tp_call */
+    0,                          /* tp_str */
+    0,                          /* tp_getattro */
+    0,                          /* tp_setattro */
+    0,                          /* tp_as_buffer */
+    Py_TPFLAGS_HAVE_GC,         /* tp_flags */
+    0,                          /* tp_doc */
+    (traverseproc)do_multi_traverse, /* tp_traverse */
+    (inquiry)do_multi_clear     /* tp_clear */
+    /* More fields follow here, depending on your Python version. You can
+     * safely ignore any compiler warnings about missing initializers.
+     */
+};
+
+
+/*************************************************************************
+// module level
+// Note that the object constructors (do_curl_new, do_multi_new)
+// are module-level functions as well.
+**************************************************************************/
+
+static PyObject *
+do_global_init(PyObject *dummy, PyObject *args)
+{
+    int res, option;
+
+    UNUSED(dummy);
+    if (!PyArg_ParseTuple(args, "i:global_init", &option)) {
+        return NULL;
+    }
+
+    if (!(option == CURL_GLOBAL_SSL ||
+          option == CURL_GLOBAL_WIN32 ||
+          option == CURL_GLOBAL_ALL ||
+          option == CURL_GLOBAL_NOTHING)) {
+        PyErr_SetString(PyExc_ValueError, "invalid option to global_init");
+        return NULL;
+    }
+
+    res = curl_global_init(option);
+    if (res != CURLE_OK) {
+        PyErr_SetString(ErrorObject, "unable to set global option");
+        return NULL;
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+static PyObject *
+do_global_cleanup(PyObject *dummy)
+{
+    UNUSED(dummy);
+    curl_global_cleanup();
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+
+static PyObject *vi_str(const char *s)
+{
+    if (s == NULL) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+    while (*s == ' ' || *s == '\t')
+        s++;
+    return PyString_FromString(s);
+}
+
+static PyObject *
+do_version_info(PyObject *dummy, PyObject *args)
+{
+    const curl_version_info_data *vi;
+    PyObject *ret = NULL;
+    PyObject *protocols = NULL;
+    PyObject *tmp;
+    int i;
+    int stamp = CURLVERSION_NOW;
+
+    UNUSED(dummy);
+    if (!PyArg_ParseTuple(args, "|i:version_info", &stamp)) {
+        return NULL;
+    }
+    vi = curl_version_info((CURLversion) stamp);
+    if (vi == NULL) {
+        PyErr_SetString(ErrorObject, "unable to get version info");
+        return NULL;
+    }
+
+    /* Note: actually libcurl in lib/version.c does ignore
+     * the "stamp" parm, and so do we */
+
+    for (i = 0; vi->protocols[i] != NULL; )
+        i++;
+    protocols = PyTuple_New(i);
+    if (protocols == NULL)
+        goto error;
+    for (i = 0; vi->protocols[i] != NULL; i++) {
+        tmp = vi_str(vi->protocols[i]);
+        if (tmp == NULL)
+            goto error;
+        PyTuple_SET_ITEM(protocols, i, tmp);
+    }
+    ret = PyTuple_New(12);
+    if (ret == NULL)
+        goto error;
+
+#define SET(i, v) \
+        tmp = (v); if (tmp == NULL) goto error; PyTuple_SET_ITEM(ret, i, tmp)
+    SET(0, PyInt_FromLong((long) vi->age));
+    SET(1, vi_str(vi->version));
+    SET(2, PyInt_FromLong(vi->version_num));
+    SET(3, vi_str(vi->host));
+    SET(4, PyInt_FromLong(vi->features));
+    SET(5, vi_str(vi->ssl_version));
+    SET(6, PyInt_FromLong(vi->ssl_version_num));
+    SET(7, vi_str(vi->libz_version));
+    SET(8, protocols);
+    SET(9, vi_str(vi->ares));
+    SET(10, PyInt_FromLong(vi->ares_num));
+    SET(11, vi_str(vi->libidn));
+#undef SET
+    return ret;
+
+error:
+    Py_XDECREF(ret);
+    Py_XDECREF(protocols);
+    return NULL;
+}
+
+
+/* Per function docstrings */
+static char pycurl_global_init_doc [] =
+"global_init(option) -> None.  Initialize curl environment.\n";
+
+static char pycurl_global_cleanup_doc [] =
+"global_cleanup() -> None.  Cleanup curl environment.\n";
+
+static char pycurl_version_info_doc [] =
+"version_info() -> tuple.  Returns a 12-tuple with the version info.\n";
+
+static char pycurl_curl_new_doc [] =
+"Curl() -> New curl object.  Implicitly calls global_init() if not called.\n";
+
+static char pycurl_multi_new_doc [] =
+"CurlMulti() -> New curl multi-object.\n";
+
+
+/* List of functions defined in this module */
+static PyMethodDef curl_methods[] = {
+    {"global_init", (PyCFunction)do_global_init, METH_VARARGS, pycurl_global_init_doc},
+    {"global_cleanup", (PyCFunction)do_global_cleanup, METH_NOARGS, pycurl_global_cleanup_doc},
+    {"version_info", (PyCFunction)do_version_info, METH_VARARGS, pycurl_version_info_doc},
+    {"Curl", (PyCFunction)do_curl_new, METH_NOARGS, pycurl_curl_new_doc},
+    {"CurlMulti", (PyCFunction)do_multi_new, METH_NOARGS, pycurl_multi_new_doc},
+    {NULL, NULL, 0, NULL}
+};
+
+
+/* Module docstring */
+static char module_doc [] =
+"This module implements an interface to the cURL library.\n"
+"\n"
+"Types:\n"
+"\n"
+"Curl() -> New object.  Create a new curl object.\n"
+"CurlMulti() -> New object.  Create a new curl multi-object.\n"
+"\n"
+"Functions:\n"
+"\n"
+"global_init(option) -> None.  Initialize curl environment.\n"
+"global_cleanup() -> None.  Cleanup curl environment.\n"
+"version_info() -> tuple.  Return version information.\n"
+;
+
+
+/* Helper functions for inserting constants into the module namespace */
+
+static void
+insobj2(PyObject *dict1, PyObject *dict2, char *name, PyObject *value)
+{
+    /* Insert an object into one or two dicts. Eats a reference to value.
+     * See also the implementation of PyDict_SetItemString(). */
+    PyObject *key = NULL;
+
+    if (dict1 == NULL && dict2 == NULL)
+        goto error;
+    if (value == NULL)
+        goto error;
+    key = PyString_FromString(name);
+    if (key == NULL)
+        goto error;
+#if 0
+    PyString_InternInPlace(&key);   /* XXX Should we really? */
+#endif
+    if (dict1 != NULL) {
+        assert(PyDict_GetItem(dict1, key) == NULL);
+        if (PyDict_SetItem(dict1, key, value) != 0)
+            goto error;
+    }
+    if (dict2 != NULL && dict2 != dict1) {
+        assert(PyDict_GetItem(dict2, key) == NULL);
+        if (PyDict_SetItem(dict2, key, value) != 0)
+            goto error;
+    }
+    Py_DECREF(key);
+    Py_DECREF(value);
+    return;
+error:
+    Py_FatalError("pycurl: FATAL: insobj2() failed");
+    assert(0);
+}
+
+static void
+insstr(PyObject *d, char *name, char *value)
+{
+    PyObject *v = PyString_FromString(value);
+    insobj2(d, NULL, name, v);
+}
+
+static void
+insint(PyObject *d, char *name, long value)
+{
+    PyObject *v = PyInt_FromLong(value);
+    insobj2(d, NULL, name, v);
+}
+
+static void
+insint_c(PyObject *d, char *name, long value)
+{
+    PyObject *v = PyInt_FromLong(value);
+    insobj2(d, curlobject_constants, name, v);
+}
+
+static void
+insint_m(PyObject *d, char *name, long value)
+{
+    PyObject *v = PyInt_FromLong(value);
+    insobj2(d, curlmultiobject_constants, name, v);
+}
+
+
+/* Initialization function for the module */
+#if defined(PyMODINIT_FUNC)
+PyMODINIT_FUNC
+#else
+#if defined(__cplusplus)
+extern "C"
+#endif
+DL_EXPORT(void)
+#endif
+initpycurl(void)
+{
+    PyObject *m, *d;
+    const curl_version_info_data *vi;
+
+    /* Initialize the type of the new type objects here; doing it here
+     * is required for portability to Windows without requiring C++. */
+    p_Curl_Type = &Curl_Type;
+    p_CurlMulti_Type = &CurlMulti_Type;
+    Curl_Type.ob_type = &PyType_Type;
+    CurlMulti_Type.ob_type = &PyType_Type;
+
+    /* Create the module and add the functions */
+    m = Py_InitModule3("pycurl", curl_methods, module_doc);
+    assert(m != NULL && PyModule_Check(m));
+
+    /* Add error object to the module */
+    d = PyModule_GetDict(m);
+    assert(d != NULL);
+    ErrorObject = PyErr_NewException("pycurl.error", NULL, NULL);
+    assert(ErrorObject != NULL);
+    PyDict_SetItemString(d, "error", ErrorObject);
+
+    curlobject_constants = PyDict_New();
+    assert(curlobject_constants != NULL);
+
+    /* Add version strings to the module */
+    insstr(d, "version", curl_version());
+    insstr(d, "COMPILE_DATE", __DATE__ " " __TIME__);
+    insint(d, "COMPILE_PY_VERSION_HEX", PY_VERSION_HEX);
+    insint(d, "COMPILE_LIBCURL_VERSION_NUM", LIBCURL_VERSION_NUM);
+
+    /**
+     ** the order of these constants mostly follows <curl/curl.h>
+     **/
+
+    /* Abort curl_read_callback(). */
+    insint_c(d, "READFUNC_ABORT", CURL_READFUNC_ABORT);
+
+    /* constants for ioctl callback return values */
+    insint_c(d, "IOE_OK", CURLIOE_OK);
+    insint_c(d, "IOE_UNKNOWNCMD", CURLIOE_UNKNOWNCMD);
+    insint_c(d, "IOE_FAILRESTART", CURLIOE_FAILRESTART);
+
+    /* curl_infotype: the kind of data that is passed to information_callback */
+/* XXX do we actually need curl_infotype in pycurl ??? */
+    insint_c(d, "INFOTYPE_TEXT", CURLINFO_TEXT);
+    insint_c(d, "INFOTYPE_HEADER_IN", CURLINFO_HEADER_IN);
+    insint_c(d, "INFOTYPE_HEADER_OUT", CURLINFO_HEADER_OUT);
+    insint_c(d, "INFOTYPE_DATA_IN", CURLINFO_DATA_IN);
+    insint_c(d, "INFOTYPE_DATA_OUT", CURLINFO_DATA_OUT);
+    insint_c(d, "INFOTYPE_SSL_DATA_IN", CURLINFO_SSL_DATA_IN);
+    insint_c(d, "INFOTYPE_SSL_DATA_OUT", CURLINFO_SSL_DATA_OUT);
+
+    /* CURLcode: error codes */
+/* FIXME: lots of error codes are missing */
+    insint_c(d, "E_OK", CURLE_OK);
+    insint_c(d, "E_UNSUPPORTED_PROTOCOL", CURLE_UNSUPPORTED_PROTOCOL);
+
+    /* curl_proxytype: constants for setopt(PROXYTYPE, x) */
+    insint_c(d, "PROXYTYPE_HTTP", CURLPROXY_HTTP);
+    insint_c(d, "PROXYTYPE_SOCKS4", CURLPROXY_SOCKS4);
+    insint_c(d, "PROXYTYPE_SOCKS5", CURLPROXY_SOCKS5);
+
+    /* curl_httpauth: constants for setopt(HTTPAUTH, x) */
+    insint_c(d, "HTTPAUTH_NONE", CURLAUTH_NONE);
+    insint_c(d, "HTTPAUTH_BASIC", CURLAUTH_BASIC);
+    insint_c(d, "HTTPAUTH_DIGEST", CURLAUTH_DIGEST);
+    insint_c(d, "HTTPAUTH_GSSNEGOTIATE", CURLAUTH_GSSNEGOTIATE);
+    insint_c(d, "HTTPAUTH_NTLM", CURLAUTH_NTLM);
+    insint_c(d, "HTTPAUTH_ANY", CURLAUTH_ANY);
+    insint_c(d, "HTTPAUTH_ANYSAFE", CURLAUTH_ANYSAFE);
+
+    /* curl_ftpssl: constants for setopt(FTP_SSL, x) */
+    insint_c(d, "FTPSSL_NONE", CURLFTPSSL_NONE);
+    insint_c(d, "FTPSSL_TRY", CURLFTPSSL_TRY);
+    insint_c(d, "FTPSSL_CONTROL", CURLFTPSSL_CONTROL);
+    insint_c(d, "FTPSSL_ALL", CURLFTPSSL_ALL);
+
+    /* curl_ftpauth: constants for setopt(FTPSSLAUTH, x) */
+    insint_c(d, "FTPAUTH_DEFAULT", CURLFTPAUTH_DEFAULT);
+    insint_c(d, "FTPAUTH_SSL", CURLFTPAUTH_SSL);
+    insint_c(d, "FTPAUTH_TLS", CURLFTPAUTH_TLS);
+
+    /* curl_ftpauth: constants for setopt(FTPSSLAUTH, x) */
+    insint_c(d, "FORM_CONTENTS", CURLFORM_COPYCONTENTS);
+    insint_c(d, "FORM_FILE", CURLFORM_FILE);
+    insint_c(d, "FORM_CONTENTTYPE", CURLFORM_CONTENTTYPE);
+
+    /* CURLoption: symbolic constants for setopt() */
+/* FIXME: reorder these to match <curl/curl.h> */
+    insint_c(d, "FILE", CURLOPT_WRITEDATA);
+    insint_c(d, "URL", CURLOPT_URL);
+    insint_c(d, "PORT", CURLOPT_PORT);
+    insint_c(d, "PROXY", CURLOPT_PROXY);
+    insint_c(d, "USERPWD", CURLOPT_USERPWD);
+    insint_c(d, "PROXYUSERPWD", CURLOPT_PROXYUSERPWD);
+    insint_c(d, "RANGE", CURLOPT_RANGE);
+    insint_c(d, "INFILE", CURLOPT_READDATA);
+    /* ERRORBUFFER is not supported */
+    insint_c(d, "WRITEFUNCTION", CURLOPT_WRITEFUNCTION);
+    insint_c(d, "READFUNCTION", CURLOPT_READFUNCTION);
+    insint_c(d, "TIMEOUT", CURLOPT_TIMEOUT);
+    insint_c(d, "INFILESIZE", CURLOPT_INFILESIZE_LARGE);    /* _LARGE ! */
+    insint_c(d, "POSTFIELDS", CURLOPT_POSTFIELDS);
+    insint_c(d, "REFERER", CURLOPT_REFERER);
+    insint_c(d, "FTPPORT", CURLOPT_FTPPORT);
+    insint_c(d, "USERAGENT", CURLOPT_USERAGENT);
+    insint_c(d, "LOW_SPEED_LIMIT", CURLOPT_LOW_SPEED_LIMIT);
+    insint_c(d, "LOW_SPEED_TIME", CURLOPT_LOW_SPEED_TIME);
+    insint_c(d, "RESUME_FROM", CURLOPT_RESUME_FROM_LARGE);  /* _LARGE ! */
+    insint_c(d, "WRITEDATA", CURLOPT_WRITEDATA);
+    insint_c(d, "READDATA", CURLOPT_READDATA);
+    insint_c(d, "PROXYPORT", CURLOPT_PROXYPORT);
+    insint_c(d, "HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL);
+    insint_c(d, "VERBOSE", CURLOPT_VERBOSE);
+    insint_c(d, "HEADER", CURLOPT_HEADER);
+    insint_c(d, "NOPROGRESS", CURLOPT_NOPROGRESS);
+    insint_c(d, "NOBODY", CURLOPT_NOBODY);
+    insint_c(d, "FAILONERROR", CURLOPT_FAILONERROR);
+    insint_c(d, "UPLOAD", CURLOPT_UPLOAD);
+    insint_c(d, "POST", CURLOPT_POST);
+    insint_c(d, "FTPLISTONLY", CURLOPT_FTPLISTONLY);
+    insint_c(d, "FTPAPPEND", CURLOPT_FTPAPPEND);
+    insint_c(d, "NETRC", CURLOPT_NETRC);
+    insint_c(d, "FOLLOWLOCATION", CURLOPT_FOLLOWLOCATION);
+    insint_c(d, "TRANSFERTEXT", CURLOPT_TRANSFERTEXT);
+    insint_c(d, "PUT", CURLOPT_PUT);
+    insint_c(d, "POSTFIELDSIZE", CURLOPT_POSTFIELDSIZE_LARGE);  /* _LARGE ! */
+    insint_c(d, "COOKIE", CURLOPT_COOKIE);
+    insint_c(d, "HTTPHEADER", CURLOPT_HTTPHEADER);
+    insint_c(d, "HTTPPOST", CURLOPT_HTTPPOST);
+    insint_c(d, "SSLCERT", CURLOPT_SSLCERT);
+    insint_c(d, "SSLCERTPASSWD", CURLOPT_SSLCERTPASSWD);
+    insint_c(d, "CRLF", CURLOPT_CRLF);
+    insint_c(d, "QUOTE", CURLOPT_QUOTE);
+    insint_c(d, "POSTQUOTE", CURLOPT_POSTQUOTE);
+    insint_c(d, "PREQUOTE", CURLOPT_PREQUOTE);
+    insint_c(d, "WRITEHEADER", CURLOPT_WRITEHEADER);
+    insint_c(d, "HEADERFUNCTION", CURLOPT_HEADERFUNCTION);
+    insint_c(d, "COOKIEFILE", CURLOPT_COOKIEFILE);
+    insint_c(d, "SSLVERSION", CURLOPT_SSLVERSION);
+    insint_c(d, "TIMECONDITION", CURLOPT_TIMECONDITION);
+    insint_c(d, "TIMEVALUE", CURLOPT_TIMEVALUE);
+    insint_c(d, "CUSTOMREQUEST", CURLOPT_CUSTOMREQUEST);
+    insint_c(d, "STDERR", CURLOPT_STDERR);
+    insint_c(d, "INTERFACE", CURLOPT_INTERFACE);
+    insint_c(d, "KRB4LEVEL", CURLOPT_KRB4LEVEL);
+    insint_c(d, "PROGRESSFUNCTION", CURLOPT_PROGRESSFUNCTION);
+    insint_c(d, "SSL_VERIFYPEER", CURLOPT_SSL_VERIFYPEER);
+    insint_c(d, "CAPATH", CURLOPT_CAPATH);
+    insint_c(d, "CAINFO", CURLOPT_CAINFO);
+    insint_c(d, "OPT_FILETIME", CURLOPT_FILETIME);
+    insint_c(d, "MAXREDIRS", CURLOPT_MAXREDIRS);
+    insint_c(d, "MAXCONNECTS", CURLOPT_MAXCONNECTS);
+    insint_c(d, "CLOSEPOLICY", CURLOPT_CLOSEPOLICY);
+    insint_c(d, "FRESH_CONNECT", CURLOPT_FRESH_CONNECT);
+    insint_c(d, "FORBID_REUSE", CURLOPT_FORBID_REUSE);
+    insint_c(d, "RANDOM_FILE", CURLOPT_RANDOM_FILE);
+    insint_c(d, "EGDSOCKET", CURLOPT_EGDSOCKET);
+    insint_c(d, "CONNECTTIMEOUT", CURLOPT_CONNECTTIMEOUT);
+    insint_c(d, "HTTPGET", CURLOPT_HTTPGET);
+    insint_c(d, "SSL_VERIFYHOST", CURLOPT_SSL_VERIFYHOST);
+    insint_c(d, "COOKIEJAR", CURLOPT_COOKIEJAR);
+    insint_c(d, "SSL_CIPHER_LIST", CURLOPT_SSL_CIPHER_LIST);
+    insint_c(d, "HTTP_VERSION", CURLOPT_HTTP_VERSION);
+    insint_c(d, "FTP_USE_EPSV", CURLOPT_FTP_USE_EPSV);
+    insint_c(d, "SSLCERTTYPE", CURLOPT_SSLCERTTYPE);
+    insint_c(d, "SSLKEY", CURLOPT_SSLKEY);
+    insint_c(d, "SSLKEYTYPE", CURLOPT_SSLKEYTYPE);
+    insint_c(d, "SSLKEYPASSWD", CURLOPT_SSLKEYPASSWD);
+    insint_c(d, "SSLENGINE", CURLOPT_SSLENGINE);
+    insint_c(d, "SSLENGINE_DEFAULT", CURLOPT_SSLENGINE_DEFAULT);
+    insint_c(d, "DNS_CACHE_TIMEOUT", CURLOPT_DNS_CACHE_TIMEOUT);
+    insint_c(d, "DNS_USE_GLOBAL_CACHE", CURLOPT_DNS_USE_GLOBAL_CACHE);
+    insint_c(d, "DEBUGFUNCTION", CURLOPT_DEBUGFUNCTION);
+    insint_c(d, "BUFFERSIZE", CURLOPT_BUFFERSIZE);
+    insint_c(d, "NOSIGNAL", CURLOPT_NOSIGNAL);
+    insint_c(d, "SHARE", CURLOPT_SHARE);
+    insint_c(d, "PROXYTYPE", CURLOPT_PROXYTYPE);
+    insint_c(d, "ENCODING", CURLOPT_ENCODING);
+    insint_c(d, "HTTP200ALIASES", CURLOPT_HTTP200ALIASES);
+    insint_c(d, "UNRESTRICTED_AUTH", CURLOPT_UNRESTRICTED_AUTH);
+    insint_c(d, "FTP_USE_EPRT", CURLOPT_FTP_USE_EPRT);
+    insint_c(d, "HTTPAUTH", CURLOPT_HTTPAUTH);
+    insint_c(d, "FTP_CREATE_MISSING_DIRS", CURLOPT_FTP_CREATE_MISSING_DIRS);
+    insint_c(d, "PROXYAUTH", CURLOPT_PROXYAUTH);
+    insint_c(d, "FTP_RESPONSE_TIMEOUT", CURLOPT_FTP_RESPONSE_TIMEOUT);
+    insint_c(d, "IPRESOLVE", CURLOPT_IPRESOLVE);
+    insint_c(d, "MAXFILESIZE", CURLOPT_MAXFILESIZE_LARGE);  /* _LARGE ! */
+    insint_c(d, "INFILESIZE_LARGE", CURLOPT_INFILESIZE_LARGE);
+    insint_c(d, "RESUME_FROM_LARGE", CURLOPT_RESUME_FROM_LARGE);
+    insint_c(d, "MAXFILESIZE_LARGE", CURLOPT_MAXFILESIZE_LARGE);
+    insint_c(d, "NETRC_FILE", CURLOPT_NETRC_FILE);
+    insint_c(d, "FTP_SSL", CURLOPT_FTP_SSL);
+    insint_c(d, "POSTFIELDSIZE_LARGE", CURLOPT_POSTFIELDSIZE_LARGE);
+    insint_c(d, "TCP_NODELAY", CURLOPT_TCP_NODELAY);
+    insint_c(d, "SOURCE_USERPWD", CURLOPT_SOURCE_USERPWD);
+    insint_c(d, "SOURCE_PREQUOTE", CURLOPT_SOURCE_PREQUOTE);
+    insint_c(d, "SOURCE_POSTQUOTE", CURLOPT_SOURCE_POSTQUOTE);
+    insint_c(d, "FTPSSLAUTH", CURLOPT_FTPSSLAUTH);
+    insint_c(d, "IOCTLFUNCTION", CURLOPT_IOCTLFUNCTION);
+    insint_c(d, "IOCTLDATA", CURLOPT_IOCTLDATA);
+    insint_c(d, "SOURCE_URL", CURLOPT_SOURCE_URL);
+    insint_c(d, "SOURCE_QUOTE", CURLOPT_SOURCE_QUOTE);
+    insint_c(d, "FTP_ACCOUNT", CURLOPT_FTP_ACCOUNT);
+
+    /* constants for setopt(IPRESOLVE, x) */
+    insint_c(d, "IPRESOLVE_WHATEVER", CURL_IPRESOLVE_WHATEVER);
+    insint_c(d, "IPRESOLVE_V4", CURL_IPRESOLVE_V4);
+    insint_c(d, "IPRESOLVE_V6", CURL_IPRESOLVE_V6);
+
+    /* constants for setopt(HTTP_VERSION, x) */
+    insint_c(d, "CURL_HTTP_VERSION_NONE", CURL_HTTP_VERSION_NONE);
+    insint_c(d, "CURL_HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0);
+    insint_c(d, "CURL_HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1);
+    insint_c(d, "CURL_HTTP_VERSION_LAST", CURL_HTTP_VERSION_LAST);
+
+    /* CURL_NETRC_OPTION: constants for setopt(NETRC, x) */
+    insint_c(d, "NETRC_OPTIONAL", CURL_NETRC_OPTIONAL);
+    insint_c(d, "NETRC_IGNORED", CURL_NETRC_IGNORED);
+    insint_c(d, "NETRC_REQUIRED", CURL_NETRC_REQUIRED);
+
+    /* constants for setopt(SSLVERSION, x) */
+    insint_c(d, "SSLVERSION_DEFAULT", CURL_SSLVERSION_DEFAULT);
+    insint_c(d, "SSLVERSION_TLSv1", CURL_SSLVERSION_TLSv1);
+    insint_c(d, "SSLVERSION_SSLv2", CURL_SSLVERSION_SSLv2);
+    insint_c(d, "SSLVERSION_SSLv3", CURL_SSLVERSION_SSLv3);
+
+    /* curl_TimeCond: constants for setopt(TIMECONDITION, x) */
+    insint_c(d, "TIMECONDITION_NONE", CURL_TIMECOND_NONE);
+    insint_c(d, "TIMECONDITION_IFMODSINCE", CURL_TIMECOND_IFMODSINCE);
+    insint_c(d, "TIMECONDITION_IFUNMODSINCE", CURL_TIMECOND_IFUNMODSINCE);
+    insint_c(d, "TIMECONDITION_LASTMOD", CURL_TIMECOND_LASTMOD);
+
+    /* CURLINFO: symbolic constants for getinfo(x) */
+    insint_c(d, "EFFECTIVE_URL", CURLINFO_EFFECTIVE_URL);
+    insint_c(d, "HTTP_CODE", CURLINFO_HTTP_CODE);
+    insint_c(d, "RESPONSE_CODE", CURLINFO_HTTP_CODE);
+    insint_c(d, "TOTAL_TIME", CURLINFO_TOTAL_TIME);
+    insint_c(d, "NAMELOOKUP_TIME", CURLINFO_NAMELOOKUP_TIME);
+    insint_c(d, "CONNECT_TIME", CURLINFO_CONNECT_TIME);
+    insint_c(d, "PRETRANSFER_TIME", CURLINFO_PRETRANSFER_TIME);
+    insint_c(d, "SIZE_UPLOAD", CURLINFO_SIZE_UPLOAD);
+    insint_c(d, "SIZE_DOWNLOAD", CURLINFO_SIZE_DOWNLOAD);
+    insint_c(d, "SPEED_DOWNLOAD", CURLINFO_SPEED_DOWNLOAD);
+    insint_c(d, "SPEED_UPLOAD", CURLINFO_SPEED_UPLOAD);
+    insint_c(d, "HEADER_SIZE", CURLINFO_HEADER_SIZE);
+    insint_c(d, "REQUEST_SIZE", CURLINFO_REQUEST_SIZE);
+    insint_c(d, "SSL_VERIFYRESULT", CURLINFO_SSL_VERIFYRESULT);
+    insint_c(d, "INFO_FILETIME", CURLINFO_FILETIME);
+    insint_c(d, "CONTENT_LENGTH_DOWNLOAD", CURLINFO_CONTENT_LENGTH_DOWNLOAD);
+    insint_c(d, "CONTENT_LENGTH_UPLOAD", CURLINFO_CONTENT_LENGTH_UPLOAD);
+    insint_c(d, "STARTTRANSFER_TIME", CURLINFO_STARTTRANSFER_TIME);
+    insint_c(d, "CONTENT_TYPE", CURLINFO_CONTENT_TYPE);
+    insint_c(d, "REDIRECT_TIME", CURLINFO_REDIRECT_TIME);
+    insint_c(d, "REDIRECT_COUNT", CURLINFO_REDIRECT_COUNT);
+    insint_c(d, "HTTP_CONNECTCODE", CURLINFO_HTTP_CONNECTCODE);
+    insint_c(d, "HTTPAUTH_AVAIL", CURLINFO_HTTPAUTH_AVAIL);
+    insint_c(d, "PROXYAUTH_AVAIL", CURLINFO_PROXYAUTH_AVAIL);
+    insint_c(d, "OS_ERRNO", CURLINFO_OS_ERRNO);
+    insint_c(d, "NUM_CONNECTS", CURLINFO_NUM_CONNECTS);
+    insint_c(d, "SSL_ENGINES", CURLINFO_SSL_ENGINES);
+
+    /* curl_closepolicy: constants for setopt(CLOSEPOLICY, x) */
+    insint_c(d, "CLOSEPOLICY_OLDEST", CURLCLOSEPOLICY_OLDEST);
+    insint_c(d, "CLOSEPOLICY_LEAST_RECENTLY_USED", CURLCLOSEPOLICY_LEAST_RECENTLY_USED);
+    insint_c(d, "CLOSEPOLICY_LEAST_TRAFFIC", CURLCLOSEPOLICY_LEAST_TRAFFIC);
+    insint_c(d, "CLOSEPOLICY_SLOWEST", CURLCLOSEPOLICY_SLOWEST);
+    insint_c(d, "CLOSEPOLICY_CALLBACK", CURLCLOSEPOLICY_CALLBACK);
+
+    /* options for global_init() */
+    insint(d, "GLOBAL_SSL", CURL_GLOBAL_SSL);
+    insint(d, "GLOBAL_WIN32", CURL_GLOBAL_WIN32);
+    insint(d, "GLOBAL_ALL", CURL_GLOBAL_ALL);
+    insint(d, "GLOBAL_NOTHING", CURL_GLOBAL_NOTHING);
+    insint(d, "GLOBAL_DEFAULT", CURL_GLOBAL_DEFAULT);
+
+    /* curl_lock_data: XXX do we need this in pycurl ??? */
+    /* curl_lock_access: XXX do we need this in pycurl ??? */
+    /* CURLSHcode: XXX do we need this in pycurl ??? */
+    /* CURLSHoption: XXX do we need this in pycurl ??? */
+
+    /* CURLversion: constants for curl_version_info(x) */
+#if 0
+    /* XXX - do we need these ?? */
+    insint(d, "VERSION_FIRST", CURLVERSION_FIRST);
+    insint(d, "VERSION_SECOND", CURLVERSION_SECOND);
+    insint(d, "VERSION_THIRD", CURLVERSION_THIRD);
+    insint(d, "VERSION_NOW", CURLVERSION_NOW);
+#endif
+
+    /* version features - bitmasks for curl_version_info_data.features */
+#if 0
+    /* XXX - do we need these ?? */
+    /* XXX - should we really rename these ?? */
+    insint(d, "VERSION_FEATURE_IPV6", CURL_VERSION_IPV6);
+    insint(d, "VERSION_FEATURE_KERBEROS4", CURL_VERSION_KERBEROS4);
+    insint(d, "VERSION_FEATURE_SSL", CURL_VERSION_SSL);
+    insint(d, "VERSION_FEATURE_LIBZ", CURL_VERSION_LIBZ);
+    insint(d, "VERSION_FEATURE_NTLM", CURL_VERSION_NTLM);
+    insint(d, "VERSION_FEATURE_GSSNEGOTIATE", CURL_VERSION_GSSNEGOTIATE);
+    insint(d, "VERSION_FEATURE_DEBUG", CURL_VERSION_DEBUG);
+    insint(d, "VERSION_FEATURE_ASYNCHDNS", CURL_VERSION_ASYNCHDNS);
+    insint(d, "VERSION_FEATURE_SPNEGO", CURL_VERSION_SPNEGO);
+    insint(d, "VERSION_FEATURE_LARGEFILE", CURL_VERSION_LARGEFILE);
+    insint(d, "VERSION_FEATURE_IDN", CURL_VERSION_IDN);
+#endif
+
+    /**
+     ** the order of these constants mostly follows <curl/multi.h>
+     **/
+
+    /* CURLMcode: multi error codes */
+    insint_m(d, "E_CALL_MULTI_PERFORM", CURLM_CALL_MULTI_PERFORM);
+    insint_m(d, "E_MULTI_OK", CURLM_OK);
+    insint_m(d, "E_MULTI_BAD_HANDLE", CURLM_BAD_HANDLE);
+    insint_m(d, "E_MULTI_BAD_EASY_HANDLE", CURLM_BAD_EASY_HANDLE);
+    insint_m(d, "E_MULTI_OUT_OF_MEMORY", CURLM_OUT_OF_MEMORY);
+    insint_m(d, "E_MULTI_INTERNAL_ERROR", CURLM_INTERNAL_ERROR);
+
+    /* Check the version, as this has caused nasty problems in
+     * some cases. */
+    vi = curl_version_info(CURLVERSION_NOW);
+    if (vi == NULL) {
+        Py_FatalError("pycurl: FATAL: curl_version_info() failed");
+        assert(0);
+    }
+    if (vi->version_num < LIBCURL_VERSION_NUM) {
+        Py_FatalError("pycurl: FATAL: libcurl link-time version is older than compile-time version");
+        assert(0);
+    }
+
+    /* Finally initialize global interpreter lock */
+    PyEval_InitThreads();
+}
+
+/* vi:ts=4:et:nowrap
+ */
diff --git a/pycurl/tests/test.py b/pycurl/tests/test.py
new file mode 100644 (file)
index 0000000..de787a6
--- /dev/null
@@ -0,0 +1,74 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys, threading, time
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+class Test(threading.Thread):
+    def __init__(self, url, ofile):
+        threading.Thread.__init__(self)
+        self.curl = pycurl.Curl()
+        self.curl.setopt(pycurl.URL, url)
+        self.curl.setopt(pycurl.WRITEDATA, ofile)
+        self.curl.setopt(pycurl.FOLLOWLOCATION, 1)
+        self.curl.setopt(pycurl.MAXREDIRS, 5)
+        self.curl.setopt(pycurl.NOSIGNAL, 1)
+
+    def run(self):
+        self.curl.perform()
+        self.curl.close()
+        sys.stdout.write(".")
+        sys.stdout.flush()
+
+
+# Read list of URIs from file specified on commandline
+try:
+    urls = open(sys.argv[1]).readlines()
+except IndexError:
+    # No file was specified, show usage string
+    print "Usage: %s <file with uris to fetch>" % sys.argv[0]
+    raise SystemExit
+
+# Initialize thread array and the file number
+threads = []
+fileno = 0
+
+# Start one thread per URI in parallel
+t1 = time.time()
+for url in urls:
+    f = open(str(fileno), "wb")
+    t = Test(url, f)
+    t.start()
+    threads.append((t, f))
+    fileno = fileno + 1
+# Wait for all threads to finish
+for thread, file in threads:
+    thread.join()
+    file.close()
+t2 = time.time()
+print "\n** Multithreading, %d seconds elapsed for %d uris" % (int(t2-t1), len(urls))
+
+# Start one thread per URI in sequence
+fileno = 0
+t1 = time.time()
+for url in urls:
+    f = open(str(fileno), "wb")
+    t = Test(url, f)
+    t.start()
+    fileno = fileno + 1
+    t.join()
+    f.close()
+t2 = time.time()
+print "\n** Singlethreading, %d seconds elapsed for %d uris" % (int(t2-t1), len(urls))
diff --git a/pycurl/tests/test_cb.py b/pycurl/tests/test_cb.py
new file mode 100644 (file)
index 0000000..936293d
--- /dev/null
@@ -0,0 +1,28 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_cb.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys
+import pycurl
+
+## Callback function invoked when body data is ready
+def body(buf):
+    # Print body data to stdout
+    sys.stdout.write(buf)
+
+## Callback function invoked when header data is ready
+def header(buf):
+    # Print header data to stderr
+    sys.stderr.write(buf)
+
+c = pycurl.Curl()
+c.setopt(pycurl.URL, 'http://www.python.org/')
+c.setopt(pycurl.WRITEFUNCTION, body)
+c.setopt(pycurl.HEADERFUNCTION, header)
+c.setopt(pycurl.FOLLOWLOCATION, 1)
+c.setopt(pycurl.MAXREDIRS, 5)
+c.perform()
+c.setopt(pycurl.URL, 'http://curl.haxx.se/')
+c.perform()
+c.close()
diff --git a/pycurl/tests/test_debug.py b/pycurl/tests/test_debug.py
new file mode 100644 (file)
index 0000000..07bec02
--- /dev/null
@@ -0,0 +1,16 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_debug.py 5574 2007-10-25 20:33:17Z thierry $
+
+import pycurl
+
+def test(t, b):
+    print "debug(%d): %s" % (t, b)
+
+c = pycurl.Curl()
+c.setopt(pycurl.URL, 'http://curl.haxx.se/')
+c.setopt(pycurl.VERBOSE, 1)
+c.setopt(pycurl.DEBUGFUNCTION, test)
+c.perform()
+c.close()
diff --git a/pycurl/tests/test_getinfo.py b/pycurl/tests/test_getinfo.py
new file mode 100644 (file)
index 0000000..927048b
--- /dev/null
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_getinfo.py 5574 2007-10-25 20:33:17Z thierry $
+
+import time
+import pycurl
+
+
+## Callback function invoked when progress information is updated
+def progress(download_t, download_d, upload_t, upload_d):
+    print "Total to download %d bytes, have %d bytes so far" % \
+          (download_t, download_d)
+
+url = "http://www.cnn.com"
+
+print "Starting downloading", url
+print
+f = open("body", "wb")
+h = open("header", "wb")
+c = pycurl.Curl()
+c.setopt(c.URL, url)
+c.setopt(c.WRITEDATA, f)
+c.setopt(c.NOPROGRESS, 0)
+c.setopt(c.PROGRESSFUNCTION, progress)
+c.setopt(c.FOLLOWLOCATION, 1)
+c.setopt(c.MAXREDIRS, 5)
+c.setopt(c.WRITEHEADER, h)
+c.setopt(c.OPT_FILETIME, 1)
+c.perform()
+
+print
+print "HTTP-code:", c.getinfo(c.HTTP_CODE)
+print "Total-time:", c.getinfo(c.TOTAL_TIME)
+print "Download speed: %.2f bytes/second" % c.getinfo(c.SPEED_DOWNLOAD)
+print "Document size: %d bytes" % c.getinfo(c.SIZE_DOWNLOAD)
+print "Effective URL:", c.getinfo(c.EFFECTIVE_URL)
+print "Content-type:", c.getinfo(c.CONTENT_TYPE)
+print "Namelookup-time:", c.getinfo(c.NAMELOOKUP_TIME)
+print "Redirect-time:", c.getinfo(c.REDIRECT_TIME)
+print "Redirect-count:", c.getinfo(c.REDIRECT_COUNT)
+epoch = c.getinfo(c.INFO_FILETIME)
+print "Filetime: %d (%s)" % (epoch, time.ctime(epoch))
+print
+print "Header is in file 'header', body is in file 'body'"
+
+c.close()
+f.close()
+h.close()
diff --git a/pycurl/tests/test_gtk.py b/pycurl/tests/test_gtk.py
new file mode 100644 (file)
index 0000000..1b306f6
--- /dev/null
@@ -0,0 +1,93 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_gtk.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys, threading
+from gtk import *
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+
+class ProgressBar:
+    def __init__(self, uri):
+        self.round = 0.0
+        win = GtkDialog()
+        win.set_title("PycURL progress")
+        win.show()
+        vbox = GtkVBox(spacing=5)
+        vbox.set_border_width(10)
+        win.vbox.pack_start(vbox)
+        win.set_default_size(200, 20)
+        vbox.show()
+        label = GtkLabel("Downloading %s" % uri)
+        label.set_alignment(0, 0.5)
+        vbox.pack_start(label, expand=FALSE)
+        label.show()
+        pbar = GtkProgressBar()
+        pbar.show()
+        self.pbar = pbar
+        vbox.pack_start(pbar)
+        win.connect("destroy", self.close_app)
+        win.connect("delete_event", self.close_app)
+
+    def progress(self, download_t, download_d, upload_t, upload_d):
+        threads_enter()
+        if download_t == 0:
+            self.round = self.round + 0.1
+            if self.round >= 1.0:  self.round = 0.0
+        else:
+            self.round = float(download_d) / float(download_t)
+        self.pbar.update(self.round)
+        threads_leave()
+
+    def mainloop(self):
+        threads_enter()
+        mainloop()
+        threads_leave()
+
+    def close_app(self, *args):
+        args[0].destroy()
+        mainquit()
+
+
+class Test(threading.Thread):
+    def __init__(self, url, target_file, progress):
+        threading.Thread.__init__(self)
+        self.target_file = target_file
+        self.progress = progress
+        self.curl = pycurl.Curl()
+        self.curl.setopt(pycurl.URL, url)
+        self.curl.setopt(pycurl.WRITEDATA, self.target_file)
+        self.curl.setopt(pycurl.FOLLOWLOCATION, 1)
+        self.curl.setopt(pycurl.NOPROGRESS, 0)
+        self.curl.setopt(pycurl.PROGRESSFUNCTION, self.progress)
+        self.curl.setopt(pycurl.MAXREDIRS, 5)
+        self.curl.setopt(pycurl.NOSIGNAL, 1)
+
+    def run(self):
+        self.curl.perform()
+        self.curl.close()
+        self.target_file.close()
+        self.progress(1.0, 1.0, 0, 0)
+
+
+# Check command line args
+if len(sys.argv) < 3:
+    print "Usage: %s <URL> <filename>" % sys.argv[0]
+    raise SystemExit
+
+# Make a progress bar window
+p = ProgressBar(sys.argv[1])
+# Start thread for fetching url
+Test(sys.argv[1], open(sys.argv[2], 'wb'), p.progress).start()
+# Enter the GTK mainloop
+p.mainloop()
diff --git a/pycurl/tests/test_internals.py b/pycurl/tests/test_internals.py
new file mode 100644 (file)
index 0000000..6a6c02f
--- /dev/null
@@ -0,0 +1,253 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_internals.py 5574 2007-10-25 20:33:17Z thierry $
+
+#
+# a simple self-test
+#
+
+try:
+    # need Python 2.2 or better for garbage collection
+    from gc import get_objects
+    import gc
+    del get_objects
+    gc.enable()
+except ImportError:
+    gc = None
+import copy, os, sys
+from StringIO import StringIO
+try:
+    import cPickle
+except ImportError:
+    cPickle = None
+try:
+    import pickle
+except ImportError:
+    pickle = None
+
+# update sys.path when running in the build directory
+from util import get_sys_path
+sys.path = get_sys_path()
+
+import pycurl
+from pycurl import Curl, CurlMulti
+
+
+class opts:
+    verbose = 1
+
+if "-q" in sys.argv:
+    opts.verbose = opts.verbose - 1
+
+
+print "Python", sys.version
+print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
+print "PycURL version info", pycurl.version_info()
+print "  %s, compiled %s" % (pycurl.__file__, pycurl.COMPILE_DATE)
+
+
+# /***********************************************************************
+# // test misc
+# ************************************************************************/
+
+if 1:
+    c = Curl()
+    assert c.URL is pycurl.URL
+    del c
+
+
+# /***********************************************************************
+# // test handles
+# ************************************************************************/
+
+# remove an invalid handle: this should fail
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    try:
+        m.remove_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    del m, c
+
+
+# remove an invalid but closed handle
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    c.close()
+    m.remove_handle(c)
+    del m, c
+
+
+# add a closed handle: this should fail
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    c.close()
+    try:
+        m.add_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    m.close()
+    del m, c
+
+
+# add a handle twice: this should fail
+if 1:
+    m = CurlMulti()
+    c = Curl()
+    m.add_handle(c)
+    try:
+        m.add_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    del m, c
+
+
+# add a handle on multiple stacks: this should fail
+if 1:
+    m1 = CurlMulti()
+    m2 = CurlMulti()
+    c = Curl()
+    m1.add_handle(c)
+    try:
+        m2.add_handle(c)
+    except pycurl.error:
+        pass
+    else:
+        assert 0, "internal error"
+    del m1, m2, c
+
+
+# move a handle
+if 1:
+    m1 = CurlMulti()
+    m2 = CurlMulti()
+    c = Curl()
+    m1.add_handle(c)
+    m1.remove_handle(c)
+    m2.add_handle(c)
+    del m1, m2, c
+
+
+# /***********************************************************************
+# // test copying and pickling - copying and pickling of
+# // instances of Curl and CurlMulti is not allowed
+# ************************************************************************/
+
+if 1 and copy:
+    c = Curl()
+    m = CurlMulti()
+    try:
+        copy.copy(c)
+    except copy.Error:
+        pass
+    else:
+        assert 0, "internal error - copying should fail"
+    try:
+        copy.copy(m)
+    except copy.Error:
+        pass
+    else:
+        assert 0, "internal error - copying should fail"
+
+if 1 and pickle:
+    c = Curl()
+    m = CurlMulti()
+    fp = StringIO()
+    p = pickle.Pickler(fp, 1)
+    try:
+        p.dump(c)
+    except pickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    try:
+        p.dump(m)
+    except pickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    del c, m, fp, p
+
+if 1 and cPickle:
+    c = Curl()
+    m = CurlMulti()
+    fp = StringIO()
+    p = cPickle.Pickler(fp, 1)
+    try:
+        p.dump(c)
+    except cPickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    try:
+        p.dump(m)
+    except cPickle.PicklingError:
+        pass
+    else:
+        assert 0, "internal error - pickling should fail"
+    del c, m, fp, p
+
+
+# /***********************************************************************
+# // test refcounts
+# ************************************************************************/
+
+# basic check of reference counting (use a memory checker like valgrind)
+if 1:
+    c = Curl()
+    m = CurlMulti()
+    m.add_handle(c)
+    del m
+    m = CurlMulti()
+    c.close()
+    del m, c
+
+# basic check of cyclic garbage collection
+if 1 and gc:
+    gc.collect()
+    c = Curl()
+    c.m = CurlMulti()
+    c.m.add_handle(c)
+    # create some nasty cyclic references
+    c.c = c
+    c.c.c1 = c
+    c.c.c2 = c
+    c.c.c3 = c.c
+    c.c.c4 = c.m
+    c.m.c = c
+    c.m.m = c.m
+    c.m.c = c
+    # delete
+    gc.collect()
+    flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS
+    if opts.verbose >= 1:
+        flags = flags | gc.DEBUG_STATS
+    gc.set_debug(flags)
+    gc.collect()
+    ##print gc.get_referrers(c)
+    ##print gc.get_objects()
+    if opts.verbose >= 1:
+        print "Tracked objects:", len(gc.get_objects())
+    # The `del' below should delete these 4 objects:
+    #   Curl + internal dict, CurlMulti + internal dict
+    del c
+    gc.collect()
+    if opts.verbose >= 1:
+        print "Tracked objects:", len(gc.get_objects())
+
+
+# /***********************************************************************
+# // done
+# ************************************************************************/
+
+print "All tests passed."
diff --git a/pycurl/tests/test_memleak.py b/pycurl/tests/test_memleak.py
new file mode 100644 (file)
index 0000000..a43ef06
--- /dev/null
@@ -0,0 +1,53 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_memleak.py 5574 2007-10-25 20:33:17Z thierry $
+
+#
+# just a simple self-test
+# need Python 2.2 or better for garbage collection
+#
+
+import gc, pycurl, sys
+gc.enable()
+
+
+print "Python", sys.version
+print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
+##print "PycURL version info", pycurl.version_info()
+print "  %s, compiled %s" % (pycurl.__file__, pycurl.COMPILE_DATE)
+
+
+gc.collect()
+flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS
+if 1:
+    flags = flags | gc.DEBUG_STATS
+gc.set_debug(flags)
+gc.collect()
+
+print "Tracked objects:", len(gc.get_objects())
+
+multi = pycurl.CurlMulti()
+t = []
+for a in range(100):
+    curl = pycurl.Curl()
+    multi.add_handle(curl)
+    t.append(curl)
+
+print "Tracked objects:", len(gc.get_objects())
+
+for curl in t:
+    curl.close()
+    multi.remove_handle(curl)
+
+print "Tracked objects:", len(gc.get_objects())
+
+del curl
+del t
+del multi
+
+print "Tracked objects:", len(gc.get_objects())
+gc.collect()
+print "Tracked objects:", len(gc.get_objects())
+
+
diff --git a/pycurl/tests/test_multi.py b/pycurl/tests/test_multi.py
new file mode 100644 (file)
index 0000000..0befd4e
--- /dev/null
@@ -0,0 +1,33 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi.py 5574 2007-10-25 20:33:17Z thierry $
+
+import pycurl
+
+m = pycurl.CurlMulti()
+m.handles = []
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c1.setopt(c1.URL, 'http://curl.haxx.se')
+c2.setopt(c2.URL, 'http://cnn.com')
+c2.setopt(c2.FOLLOWLOCATION, 1)
+m.add_handle(c1)
+m.add_handle(c2)
+m.handles.append(c1)
+m.handles.append(c2)
+
+num_handles = len(m.handles)
+while num_handles:
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+    m.select()
+
+m.remove_handle(c2)
+m.remove_handle(c1)
+del m.handles
+m.close()
+c1.close()
+c2.close()
diff --git a/pycurl/tests/test_multi2.py b/pycurl/tests/test_multi2.py
new file mode 100644 (file)
index 0000000..6da93c2
--- /dev/null
@@ -0,0 +1,72 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi2.py 5574 2007-10-25 20:33:17Z thierry $
+
+import os, sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+
+urls = (
+    "http://curl.haxx.se",
+    "http://www.python.org",
+    "http://pycurl.sourceforge.net",
+    "http://pycurl.sourceforge.net/tests/403_FORBIDDEN",  # that actually exists ;-)
+    "http://pycurl.sourceforge.net/tests/404_NOT_FOUND",
+)
+
+# Read list of URIs from file specified on commandline
+try:
+    urls = open(sys.argv[1], "rb").readlines()
+except IndexError:
+    # No file was specified
+    pass
+
+# init
+m = pycurl.CurlMulti()
+m.handles = []
+for url in urls:
+    c = pycurl.Curl()
+    # save info in standard Python attributes
+    c.url = url
+    c.body = StringIO()
+    c.http_code = -1
+    m.handles.append(c)
+    # pycurl API calls
+    c.setopt(c.URL, c.url)
+    c.setopt(c.WRITEFUNCTION, c.body.write)
+    m.add_handle(c)
+
+# get data
+num_handles = len(m.handles)
+while num_handles:
+     while 1:
+         ret, num_handles = m.perform()
+         if ret != pycurl.E_CALL_MULTI_PERFORM:
+             break
+     # currently no more I/O is pending, could do something in the meantime
+     # (display a progress bar, etc.)
+     m.select()
+
+# close handles
+for c in m.handles:
+    # save info in standard Python attributes
+    c.http_code = c.getinfo(c.HTTP_CODE)
+    # pycurl API calls
+    m.remove_handle(c)
+    c.close()
+m.close()
+
+# print result
+for c in m.handles:
+    data = c.body.getvalue()
+    if 0:
+        print "**********", c.url, "**********"
+        print data
+    else:
+        print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data))
+
diff --git a/pycurl/tests/test_multi3.py b/pycurl/tests/test_multi3.py
new file mode 100644 (file)
index 0000000..8889246
--- /dev/null
@@ -0,0 +1,87 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi3.py 5574 2007-10-25 20:33:17Z thierry $
+
+# same as test_multi2.py, but enforce some debugging and strange API-calls
+
+import os, sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+
+urls = (
+    "http://curl.haxx.se",
+    "http://www.python.org",
+    "http://pycurl.sourceforge.net",
+    "http://pycurl.sourceforge.net/THIS_HANDLE_IS_CLOSED",
+)
+
+# init
+m = pycurl.CurlMulti()
+m.handles = []
+for url in urls:
+    c = pycurl.Curl()
+    # save info in standard Python attributes
+    c.url = url
+    c.body = StringIO()
+    c.http_code = -1
+    c.debug = 0
+    m.handles.append(c)
+    # pycurl API calls
+    c.setopt(c.URL, c.url)
+    c.setopt(c.WRITEFUNCTION, c.body.write)
+    m.add_handle(c)
+
+# debug - close a handle
+if 1:
+    c = m.handles[3]
+    c.debug = 1
+    c.close()
+
+# get data
+num_handles = len(m.handles)
+while num_handles:
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+    # currently no more I/O is pending, could do something in the meantime
+    # (display a progress bar, etc.)
+    m.select()
+
+# close handles
+for c in m.handles:
+    # save info in standard Python attributes
+    try:
+        c.http_code = c.getinfo(c.HTTP_CODE)
+    except pycurl.error:
+        # handle already closed - see debug above
+        assert c.debug
+        c.http_code = -1
+    # pycurl API calls
+    if 0:
+        m.remove_handle(c)
+        c.close()
+    elif 0:
+        # in the C API this is the wrong calling order, but pycurl
+        # handles this automatically
+        c.close()
+        m.remove_handle(c)
+    else:
+        # actually, remove_handle is called automatically on close
+        c.close()
+m.close()
+
+# print result
+for c in m.handles:
+    data = c.body.getvalue()
+    if 0:
+        print "**********", c.url, "**********"
+        print data
+    else:
+        print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data))
+
diff --git a/pycurl/tests/test_multi4.py b/pycurl/tests/test_multi4.py
new file mode 100644 (file)
index 0000000..5ab5be8
--- /dev/null
@@ -0,0 +1,57 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi4.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys, select, time
+import pycurl
+
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c3 = pycurl.Curl()
+c1.setopt(c1.URL, "http://www.python.org")
+c2.setopt(c2.URL, "http://curl.haxx.se")
+c3.setopt(c3.URL, "http://slashdot.org")
+c1.body = open("doc1", "wb")
+c2.body = open("doc2", "wb")
+c3.body = open("doc3", "wb")
+c1.setopt(c1.WRITEFUNCTION, c1.body.write)
+c2.setopt(c2.WRITEFUNCTION, c2.body.write)
+c3.setopt(c3.WRITEFUNCTION, c3.body.write)
+
+m = pycurl.CurlMulti()
+m.add_handle(c1)
+m.add_handle(c2)
+m.add_handle(c3)
+
+# Number of seconds to wait for a timeout to happen
+SELECT_TIMEOUT = 10
+
+# Stir the state machine into action
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM:
+        break
+
+# Keep going until all the connections have terminated
+while num_handles:
+    apply(select.select, m.fdset() + (SELECT_TIMEOUT,))
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+# Cleanup
+m.remove_handle(c3)
+m.remove_handle(c2)
+m.remove_handle(c1)
+m.close()
+c1.body.close()
+c2.body.close()
+c3.body.close()
+c1.close()
+c2.close()
+c3.close()
+print "http://www.python.org is in file doc1"
+print "http://curl.haxx.se is in file doc2"
+print "http://slashdot.org is in file doc3"
diff --git a/pycurl/tests/test_multi5.py b/pycurl/tests/test_multi5.py
new file mode 100644 (file)
index 0000000..0ddb518
--- /dev/null
@@ -0,0 +1,60 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi5.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys, select, time
+import pycurl
+
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c3 = pycurl.Curl()
+c1.setopt(c1.URL, "http://www.python.org")
+c2.setopt(c2.URL, "http://curl.haxx.se")
+c3.setopt(c3.URL, "http://slashdot.org")
+c1.body = open("doc1", "wb")
+c2.body = open("doc2", "wb")
+c3.body = open("doc3", "wb")
+c1.setopt(c1.WRITEFUNCTION, c1.body.write)
+c2.setopt(c2.WRITEFUNCTION, c2.body.write)
+c3.setopt(c3.WRITEFUNCTION, c3.body.write)
+
+m = pycurl.CurlMulti()
+m.add_handle(c1)
+m.add_handle(c2)
+m.add_handle(c3)
+
+# Number of seconds to wait for a timeout to happen
+SELECT_TIMEOUT = 10
+
+# Stir the state machine into action
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM:
+        break
+
+# Keep going until all the connections have terminated
+while num_handles:
+    # The select method uses fdset internally to determine which file descriptors
+    # to check.
+    m.select(SELECT_TIMEOUT)
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+# Cleanup
+m.remove_handle(c3)
+m.remove_handle(c2)
+m.remove_handle(c1)
+m.close()
+c1.body.close()
+c2.body.close()
+c3.body.close()
+c1.close()
+c2.close()
+c3.close()
+print "http://www.python.org is in file doc1"
+print "http://curl.haxx.se is in file doc2"
+print "http://slashdot.org is in file doc3"
+
diff --git a/pycurl/tests/test_multi6.py b/pycurl/tests/test_multi6.py
new file mode 100644 (file)
index 0000000..714c5c8
--- /dev/null
@@ -0,0 +1,62 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi6.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys, select, time
+import pycurl
+
+c1 = pycurl.Curl()
+c2 = pycurl.Curl()
+c3 = pycurl.Curl()
+c1.setopt(c1.URL, "http://www.python.org")
+c2.setopt(c2.URL, "http://curl.haxx.se")
+c3.setopt(c3.URL, "http://slashdot.org")
+c1.body = open("doc1", "wb")
+c2.body = open("doc2", "wb")
+c3.body = open("doc3", "wb")
+c1.setopt(c1.WRITEFUNCTION, c1.body.write)
+c2.setopt(c2.WRITEFUNCTION, c2.body.write)
+c3.setopt(c3.WRITEFUNCTION, c3.body.write)
+
+m = pycurl.CurlMulti()
+m.add_handle(c1)
+m.add_handle(c2)
+m.add_handle(c3)
+
+# Number of seconds to wait for a timeout to happen
+SELECT_TIMEOUT = 10
+
+# Stir the state machine into action
+while 1:
+    ret, num_handles = m.perform()
+    if ret != pycurl.E_CALL_MULTI_PERFORM:
+        break
+
+# Keep going until all the connections have terminated
+while num_handles:
+    # The select method uses fdset internally to determine which file descriptors
+    # to check.
+    m.select(SELECT_TIMEOUT)
+    while 1:
+        ret, num_handles = m.perform()
+        # Print the message, if any
+        print m.info_read(1)
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+# Cleanup
+m.remove_handle(c3)
+m.remove_handle(c2)
+m.remove_handle(c1)
+m.close()
+c1.body.close()
+c2.body.close()
+c3.body.close()
+c1.close()
+c2.close()
+c3.close()
+print "http://www.python.org is in file doc1"
+print "http://curl.haxx.se is in file doc2"
+print "http://slashdot.org is in file doc3"
+
diff --git a/pycurl/tests/test_multi_vs_thread.py b/pycurl/tests/test_multi_vs_thread.py
new file mode 100644 (file)
index 0000000..8dac806
--- /dev/null
@@ -0,0 +1,262 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_multi_vs_thread.py 5574 2007-10-25 20:33:17Z thierry $
+
+import os, sys, time
+from threading import Thread, RLock
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
+# the libcurl tutorial for more info.
+try:
+    import signal
+    from signal import SIGPIPE, SIG_IGN
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+except ImportError:
+    pass
+
+# The conclusion is: the multi interface is fastest!
+
+NUM_PAGES = 30
+NUM_THREADS = 10
+assert NUM_PAGES % NUM_THREADS == 0
+
+##URL = "http://pycurl.sourceforge.net/tests/testgetvars.php?%d"
+URL = "http://pycurl.sourceforge.net/tests/teststaticpage.html?%d"
+
+
+#
+# util
+#
+
+class Curl:
+    def __init__(self, url):
+        self.url = url
+        self.body = StringIO()
+        self.http_code = -1
+        # pycurl API calls
+        self._curl = pycurl.Curl()
+        self._curl.setopt(pycurl.URL, self.url)
+        self._curl.setopt(pycurl.WRITEFUNCTION, self.body.write)
+        self._curl.setopt(pycurl.NOSIGNAL, 1)
+
+    def perform(self):
+        self._curl.perform()
+
+    def close(self):
+        self.http_code = self._curl.getinfo(pycurl.HTTP_CODE)
+        self._curl.close()
+
+
+def print_result(items):
+    return  # DO NOTHING
+    #
+    for c in items:
+        data = c.body.getvalue()
+        if 0:
+            print "**********", c.url, "**********"
+            print data
+        elif 1:
+            print "%-60s   %3d   %6d" % (c.url, c.http_code, len(data))
+
+
+###
+### 1) multi
+###
+
+def test_multi():
+    clock1 = time.time()
+
+    # init
+    handles = []
+    m = pycurl.CurlMulti()
+    for i in range(NUM_PAGES):
+        c = Curl(URL %i)
+        m.add_handle(c._curl)
+        handles.append(c)
+
+    clock2 = time.time()
+
+    # stir state machine into action
+    while 1:
+        ret, num_handles = m.perform()
+        if ret != pycurl.E_CALL_MULTI_PERFORM:
+            break
+
+    # get data
+    while num_handles:
+        m.select()
+        while 1:
+            ret, num_handles = m.perform()
+            if ret != pycurl.E_CALL_MULTI_PERFORM:
+                break
+
+    clock3 = time.time()
+
+    # close handles
+    for c in handles:
+        c.close()
+    m.close()
+
+    clock4 = time.time()
+    print "multi  interface:        %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+
+    # print result
+    print_result(handles)
+
+
+
+###
+### 2) thread
+###
+
+class Test(Thread):
+    def __init__(self, lock=None):
+        Thread.__init__(self)
+        self.lock = lock
+        self.items = []
+
+    def run(self):
+        if self.lock:
+            self.lock.acquire()
+            self.lock.release()
+        for c in self.items:
+            c.perform()
+
+
+def test_threads(lock=None):
+    clock1 = time.time()
+
+    # create and start threads, but block them
+    if lock:
+        lock.acquire()
+
+    # init (FIXME - this is ugly)
+    threads = []
+    handles = []
+    t = None
+    for i in range(NUM_PAGES):
+        if i % (NUM_PAGES / NUM_THREADS) == 0:
+            t = Test(lock)
+            if lock:
+                t.start()
+            threads.append(t)
+        c = Curl(URL % i)
+        t.items.append(c)
+        handles.append(c)
+    assert len(handles) == NUM_PAGES
+    assert len(threads) == NUM_THREADS
+
+    clock2 = time.time()
+
+    #
+    if lock:
+        # release lock to let the blocked threads run
+        lock.release()
+    else:
+        # start threads
+        for t in threads:
+            t.start()
+    # wait for threads to finish
+    for t in threads:
+        t.join()
+
+    clock3 = time.time()
+
+    # close handles
+    for c in handles:
+        c.close()
+
+    clock4 = time.time()
+    if lock:
+        print "thread interface [lock]: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+    else:
+        print "thread interface:        %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+
+    # print result
+    print_result(handles)
+
+
+
+###
+### 3) thread - threads grab curl objects on demand from a shared pool
+###
+
+class TestPool(Thread):
+    def __init__(self, lock, pool):
+        Thread.__init__(self)
+        self.lock = lock
+        self.pool = pool
+
+    def run(self):
+        while 1:
+            self.lock.acquire()
+            c = None
+            if self.pool:
+                c = self.pool.pop()
+            self.lock.release()
+            if c is None:
+                break
+            c.perform()
+
+
+def test_thread_pool(lock):
+    clock1 = time.time()
+
+    # init
+    handles = []
+    for i in range(NUM_PAGES):
+        c = Curl(URL %i)
+        handles.append(c)
+
+    # create and start threads, but block them
+    lock.acquire()
+    threads = []
+    pool = handles[:]   # shallow copy of the list, shared for pop()
+    for i in range(NUM_THREADS):
+        t = TestPool(lock, pool)
+        t.start()
+        threads.append(t)
+    assert len(pool) == NUM_PAGES
+    assert len(threads) == NUM_THREADS
+
+    clock2 = time.time()
+
+    # release lock to let the blocked threads run
+    lock.release()
+
+    # wait for threads to finish
+    for t in threads:
+        t.join()
+
+    clock3 = time.time()
+
+    # close handles
+    for c in handles:
+        c.close()
+
+    clock4 = time.time()
+    print "thread interface [pool]: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1)
+
+    # print result
+    print_result(handles)
+
+
+
+lock = RLock()
+if 1:
+    test_multi()
+    test_threads()
+    test_threads(lock)
+    test_thread_pool(lock)
+else:
+    test_thread_pool(lock)
+    test_threads(lock)
+    test_threads()
+    test_multi()
+
diff --git a/pycurl/tests/test_post.py b/pycurl/tests/test_post.py
new file mode 100644 (file)
index 0000000..17caff6
--- /dev/null
@@ -0,0 +1,24 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_post.py 5574 2007-10-25 20:33:17Z thierry $
+
+import urllib
+import pycurl
+
+# simple
+pf = {'field1': 'value1'}
+
+# multiple fields
+pf = {'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'}
+
+# multiple fields with & in field
+pf = {'field1':'value1', 'field2':'value2 with blanks and & chars',
+      'field3':'value3'}
+
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://pycurl.sourceforge.net/tests/testpostvars.php')
+c.setopt(c.POSTFIELDS, urllib.urlencode(pf))
+c.setopt(c.VERBOSE, 1)
+c.perform()
+c.close()
diff --git a/pycurl/tests/test_post2.py b/pycurl/tests/test_post2.py
new file mode 100644 (file)
index 0000000..60f02fa
--- /dev/null
@@ -0,0 +1,18 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_post2.py 5574 2007-10-25 20:33:17Z thierry $
+
+import pycurl
+
+pf = [('field1', 'this is a test using httppost & stuff'),
+      ('field2', (pycurl.FORM_FILE, 'test_post.py', pycurl.FORM_FILE, 'test_post2.py')),
+      ('field3', (pycurl.FORM_CONTENTS, 'this is wei\000rd, but null-bytes are okay'))
+     ]
+
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://www.contactor.se/~dast/postit.cgi')
+c.setopt(c.HTTPPOST, pf)
+c.setopt(c.VERBOSE, 1)
+c.perform()
+c.close()
diff --git a/pycurl/tests/test_post3.py b/pycurl/tests/test_post3.py
new file mode 100644 (file)
index 0000000..8a9ea93
--- /dev/null
@@ -0,0 +1,32 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_post3.py 5574 2007-10-25 20:33:17Z thierry $
+
+import urllib
+POSTSTRING = urllib.urlencode({'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'})
+
+class test:
+
+    def __init__(self):
+        self.finished = False
+
+    def read_cb(self, size):
+        assert len(POSTSTRING) <= size
+        if not self.finished:
+            self.finished = True
+            return POSTSTRING
+        else:
+            # Nothing more to read
+            return ""
+
+import pycurl
+c = pycurl.Curl()
+t = test()
+c.setopt(c.URL, 'http://pycurl.sourceforge.net/tests/testpostvars.php')
+c.setopt(c.POST, 1)
+c.setopt(c.POSTFIELDSIZE, len(POSTSTRING))
+c.setopt(c.READFUNCTION, t.read_cb)
+c.setopt(c.VERBOSE, 1)
+c.perform()
+c.close()
diff --git a/pycurl/tests/test_stringio.py b/pycurl/tests/test_stringio.py
new file mode 100644 (file)
index 0000000..21a3153
--- /dev/null
@@ -0,0 +1,25 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_stringio.py 5574 2007-10-25 20:33:17Z thierry $
+
+import sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+import pycurl
+
+url = "http://curl.haxx.se/dev/"
+
+print "Testing", pycurl.version
+
+body = StringIO()
+c = pycurl.Curl()
+c.setopt(c.URL, url)
+c.setopt(c.WRITEFUNCTION, body.write)
+c.perform()
+c.close()
+
+contents = body.getvalue()
+print contents
diff --git a/pycurl/tests/test_xmlrpc.py b/pycurl/tests/test_xmlrpc.py
new file mode 100644 (file)
index 0000000..918f76f
--- /dev/null
@@ -0,0 +1,29 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: test_xmlrpc.py 5574 2007-10-25 20:33:17Z thierry $
+
+## XML-RPC lib included in python2.2
+import xmlrpclib
+import pycurl
+
+# Header fields passed in request
+xmlrpc_header = [
+    "User-Agent: PycURL XML-RPC Test", "Content-Type: text/xml"
+    ]
+
+# XML-RPC request template
+xmlrpc_template = """
+<?xml version='1.0'?><methodCall><methodName>%s</methodName>%s</methodCall>
+"""
+
+# Engage
+c = pycurl.Curl()
+c.setopt(c.URL, 'http://betty.userland.com/RPC2')
+c.setopt(c.POST, 1)
+c.setopt(c.HTTPHEADER, xmlrpc_header)
+c.setopt(c.POSTFIELDS, xmlrpc_template % ("examples.getStateName", xmlrpclib.dumps((5,))))
+
+print 'Response from http://betty.userland.com/'
+c.perform()
+c.close()
diff --git a/pycurl/tests/util.py b/pycurl/tests/util.py
new file mode 100644 (file)
index 0000000..e4e9607
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- coding: iso-8859-1 -*-
+# vi:ts=4:et
+# $Id: util.py 5574 2007-10-25 20:33:17Z thierry $
+
+import os, sys
+
+#
+# prepare sys.path in case we are still in the build directory
+# see also: distutils/command/build.py (build_platlib)
+#
+
+def get_sys_path(p=None):
+    if p is None: p = sys.path
+    p = p[:]
+    try:
+        from distutils.util import get_platform
+    except ImportError:
+        return p
+    p0 = ""
+    if p: p0 = p[0]
+    #
+    plat = get_platform()
+    plat_specifier = "%s-%s" % (plat, sys.version[:3])
+    ##print plat, plat_specifier
+    #
+    for prefix in (p0, os.curdir, os.pardir,):
+        if not prefix:
+            continue
+        d = os.path.join(prefix, "build")
+        for subdir in ("lib", "lib." + plat_specifier, "lib." + plat):
+            dir = os.path.normpath(os.path.join(d, subdir))
+            if os.path.isdir(dir):
+                if dir not in p:
+                    p.insert(1, dir)
+    #
+    return p
+
+