From dc3a59545e6bd92a22d85d946f9722f370a7d317 Mon Sep 17 00:00:00 2001 From: Tony Mack Date: Fri, 10 Oct 2008 19:42:41 +0000 Subject: [PATCH] starting new development to support slice conf files --- php/.cvsignore | 2 + php/Makefile | 25 + php/footer.php | 9 + php/header.php | 202 ++ php/methods.py | 111 + php/xmlrpc/CREDITS | 2 + php/xmlrpc/EXPERIMENTAL | 5 + php/xmlrpc/Makefile | 32 + php/xmlrpc/config.m4 | 93 + php/xmlrpc/config.m4.lib64 | 93 + php/xmlrpc/config.w32 | 14 + php/xmlrpc/libxmlrpc/README | 17 + php/xmlrpc/libxmlrpc/acinclude.m4 | 32 + php/xmlrpc/libxmlrpc/base64.c | 192 ++ php/xmlrpc/libxmlrpc/base64.h | 38 + php/xmlrpc/libxmlrpc/encodings.c | 118 + php/xmlrpc/libxmlrpc/encodings.h | 46 + php/xmlrpc/libxmlrpc/queue.c | 982 ++++++ php/xmlrpc/libxmlrpc/queue.h | 89 + php/xmlrpc/libxmlrpc/simplestring.c | 251 ++ php/xmlrpc/libxmlrpc/simplestring.h | 76 + php/xmlrpc/libxmlrpc/system_methods.c | 378 +++ php/xmlrpc/libxmlrpc/system_methods_private.h | 91 + php/xmlrpc/libxmlrpc/xml_element.c | 750 +++++ php/xmlrpc/libxmlrpc/xml_element.c.gcc4 | 747 +++++ php/xmlrpc/libxmlrpc/xml_element.h | 202 ++ php/xmlrpc/libxmlrpc/xml_to_dandarpc.c | 319 ++ php/xmlrpc/libxmlrpc/xml_to_dandarpc.h | 44 + php/xmlrpc/libxmlrpc/xml_to_soap.c | 670 ++++ php/xmlrpc/libxmlrpc/xml_to_soap.h | 44 + php/xmlrpc/libxmlrpc/xml_to_xmlrpc.c | 413 +++ php/xmlrpc/libxmlrpc/xml_to_xmlrpc.h | 45 + php/xmlrpc/libxmlrpc/xmlrpc.c | 2963 +++++++++++++++++ php/xmlrpc/libxmlrpc/xmlrpc.h | 455 +++ php/xmlrpc/libxmlrpc/xmlrpc.m4 | 12 + php/xmlrpc/libxmlrpc/xmlrpc_introspection.c | 604 ++++ php/xmlrpc/libxmlrpc/xmlrpc_introspection.h | 101 + .../libxmlrpc/xmlrpc_introspection_private.h | 106 + php/xmlrpc/libxmlrpc/xmlrpc_private.h | 178 + php/xmlrpc/libxmlrpc/xmlrpc_win32.h | 11 + php/xmlrpc/php_xmlrpc.h | 132 + php/xmlrpc/xmlrpc-epi-php.c | 1528 +++++++++ php/xmlrpc/xmlrpc.dsp | 211 ++ psycopg2/AUTHORS | 8 + psycopg2/LICENSE | 60 + psycopg2/MANIFEST.in | 12 + psycopg2/doc/ChangeLog-1.x | 1744 ++++++++++ psycopg2/doc/SUCCESS | 114 + psycopg2/doc/TODO | 33 + psycopg2/doc/api-screen.css | 138 + psycopg2/doc/async.txt | 67 + psycopg2/doc/extensions.html | 219 ++ psycopg2/doc/extensions.rst | 260 ++ pycurl/COPYING | 504 +++ pycurl/ChangeLog | 759 +++++ pycurl/INSTALL | 44 + pycurl/MANIFEST.in | 22 + pycurl/Makefile | 60 + pycurl/PKG-INFO | 11 + pycurl/README | 12 + pycurl/TODO | 27 + pycurl/doc/callbacks.html | 140 + pycurl/doc/curlmultiobject.html | 136 + pycurl/doc/curlobject.html | 102 + pycurl/doc/pycurl.html | 120 + pycurl/examples/basicfirst.py | 25 + pycurl/examples/file_upload.py | 46 + pycurl/examples/linksys.py | 563 ++++ pycurl/examples/retriever-multi.py | 122 + pycurl/examples/retriever.py | 99 + pycurl/examples/sfquery.py | 64 + pycurl/examples/xmlrpc_curl.py | 61 + pycurl/python/curl/__init__.py | 146 + pycurl/setup.py | 199 ++ pycurl/setup_win32_ssl.py | 34 + pycurl/src/Makefile | 19 + pycurl/src/pycurl.c | 2828 ++++++++++++++++ pycurl/tests/test.py | 74 + pycurl/tests/test_cb.py | 28 + pycurl/tests/test_debug.py | 16 + pycurl/tests/test_getinfo.py | 49 + pycurl/tests/test_gtk.py | 93 + pycurl/tests/test_internals.py | 253 ++ pycurl/tests/test_memleak.py | 53 + pycurl/tests/test_multi.py | 33 + pycurl/tests/test_multi2.py | 72 + pycurl/tests/test_multi3.py | 87 + pycurl/tests/test_multi4.py | 57 + pycurl/tests/test_multi5.py | 60 + pycurl/tests/test_multi6.py | 62 + pycurl/tests/test_multi_vs_thread.py | 262 ++ pycurl/tests/test_post.py | 24 + pycurl/tests/test_post2.py | 18 + pycurl/tests/test_post3.py | 32 + pycurl/tests/test_stringio.py | 25 + pycurl/tests/test_xmlrpc.py | 29 + pycurl/tests/util.py | 38 + 97 files changed, 22596 insertions(+) create mode 100644 php/.cvsignore create mode 100644 php/Makefile create mode 100644 php/footer.php create mode 100644 php/header.php create mode 100755 php/methods.py create mode 100644 php/xmlrpc/CREDITS create mode 100644 php/xmlrpc/EXPERIMENTAL create mode 100644 php/xmlrpc/Makefile create mode 100644 php/xmlrpc/config.m4 create mode 100644 php/xmlrpc/config.m4.lib64 create mode 100644 php/xmlrpc/config.w32 create mode 100644 php/xmlrpc/libxmlrpc/README create mode 100644 php/xmlrpc/libxmlrpc/acinclude.m4 create mode 100644 php/xmlrpc/libxmlrpc/base64.c create mode 100644 php/xmlrpc/libxmlrpc/base64.h create mode 100644 php/xmlrpc/libxmlrpc/encodings.c create mode 100644 php/xmlrpc/libxmlrpc/encodings.h create mode 100644 php/xmlrpc/libxmlrpc/queue.c create mode 100644 php/xmlrpc/libxmlrpc/queue.h create mode 100644 php/xmlrpc/libxmlrpc/simplestring.c create mode 100644 php/xmlrpc/libxmlrpc/simplestring.h create mode 100644 php/xmlrpc/libxmlrpc/system_methods.c create mode 100644 php/xmlrpc/libxmlrpc/system_methods_private.h create mode 100644 php/xmlrpc/libxmlrpc/xml_element.c create mode 100644 php/xmlrpc/libxmlrpc/xml_element.c.gcc4 create mode 100644 php/xmlrpc/libxmlrpc/xml_element.h create mode 100644 php/xmlrpc/libxmlrpc/xml_to_dandarpc.c create mode 100644 php/xmlrpc/libxmlrpc/xml_to_dandarpc.h create mode 100644 php/xmlrpc/libxmlrpc/xml_to_soap.c create mode 100644 php/xmlrpc/libxmlrpc/xml_to_soap.h create mode 100644 php/xmlrpc/libxmlrpc/xml_to_xmlrpc.c create mode 100644 php/xmlrpc/libxmlrpc/xml_to_xmlrpc.h create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc.c create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc.h create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc.m4 create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc_introspection.c create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc_introspection.h create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc_introspection_private.h create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc_private.h create mode 100644 php/xmlrpc/libxmlrpc/xmlrpc_win32.h create mode 100644 php/xmlrpc/php_xmlrpc.h create mode 100644 php/xmlrpc/xmlrpc-epi-php.c create mode 100644 php/xmlrpc/xmlrpc.dsp create mode 100644 psycopg2/AUTHORS create mode 100644 psycopg2/LICENSE create mode 100644 psycopg2/MANIFEST.in create mode 100644 psycopg2/doc/ChangeLog-1.x create mode 100644 psycopg2/doc/SUCCESS create mode 100644 psycopg2/doc/TODO create mode 100644 psycopg2/doc/api-screen.css create mode 100644 psycopg2/doc/async.txt create mode 100644 psycopg2/doc/extensions.html create mode 100644 psycopg2/doc/extensions.rst create mode 100644 pycurl/COPYING create mode 100644 pycurl/ChangeLog create mode 100644 pycurl/INSTALL create mode 100644 pycurl/MANIFEST.in create mode 100644 pycurl/Makefile create mode 100644 pycurl/PKG-INFO create mode 100644 pycurl/README create mode 100644 pycurl/TODO create mode 100644 pycurl/doc/callbacks.html create mode 100644 pycurl/doc/curlmultiobject.html create mode 100644 pycurl/doc/curlobject.html create mode 100644 pycurl/doc/pycurl.html create mode 100644 pycurl/examples/basicfirst.py create mode 100644 pycurl/examples/file_upload.py create mode 100755 pycurl/examples/linksys.py create mode 100644 pycurl/examples/retriever-multi.py create mode 100644 pycurl/examples/retriever.py create mode 100644 pycurl/examples/sfquery.py create mode 100644 pycurl/examples/xmlrpc_curl.py create mode 100644 pycurl/python/curl/__init__.py create mode 100644 pycurl/setup.py create mode 100644 pycurl/setup_win32_ssl.py create mode 100644 pycurl/src/Makefile create mode 100644 pycurl/src/pycurl.c create mode 100644 pycurl/tests/test.py create mode 100644 pycurl/tests/test_cb.py create mode 100644 pycurl/tests/test_debug.py create mode 100644 pycurl/tests/test_getinfo.py create mode 100644 pycurl/tests/test_gtk.py create mode 100644 pycurl/tests/test_internals.py create mode 100644 pycurl/tests/test_memleak.py create mode 100644 pycurl/tests/test_multi.py create mode 100644 pycurl/tests/test_multi2.py create mode 100644 pycurl/tests/test_multi3.py create mode 100644 pycurl/tests/test_multi4.py create mode 100644 pycurl/tests/test_multi5.py create mode 100644 pycurl/tests/test_multi6.py create mode 100644 pycurl/tests/test_multi_vs_thread.py create mode 100644 pycurl/tests/test_post.py create mode 100644 pycurl/tests/test_post2.py create mode 100644 pycurl/tests/test_post3.py create mode 100644 pycurl/tests/test_stringio.py create mode 100644 pycurl/tests/test_xmlrpc.py create mode 100644 pycurl/tests/util.py diff --git a/php/.cvsignore b/php/.cvsignore new file mode 100644 index 00000000..dd89eef5 --- /dev/null +++ b/php/.cvsignore @@ -0,0 +1,2 @@ +methods.php +plc_api.php diff --git a/php/Makefile b/php/Makefile new file mode 100644 index 00000000..d2eeb3fc --- /dev/null +++ b/php/Makefile @@ -0,0 +1,25 @@ +# +# (Re)builds PHP API. PHP classes must be defined in a single file. +# +# Mark Huang +# 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 index 00000000..62123043 --- /dev/null +++ b/php/footer.php @@ -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 index 00000000..64657986 --- /dev/null +++ b/php/header.php @@ -0,0 +1,202 @@ + +// 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. 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 index 00000000..ad7944f8 --- /dev/null +++ b/php/methods.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# +# Generates the PLCAPI interface for the website PHP code. +# +# Mark Huang +# 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 index 00000000..cfb14faf --- /dev/null +++ b/php/xmlrpc/CREDITS @@ -0,0 +1,2 @@ +xmlrpc +Dan Libby diff --git a/php/xmlrpc/EXPERIMENTAL b/php/xmlrpc/EXPERIMENTAL new file mode 100644 index 00000000..6443e996 --- /dev/null +++ b/php/xmlrpc/EXPERIMENTAL @@ -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 index 00000000..e4fea324 --- /dev/null +++ b/php/xmlrpc/Makefile @@ -0,0 +1,32 @@ +# +# Build xmlrpc.so PHP extension +# +# Mark Huang +# 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 index 00000000..5a1a1e88 --- /dev/null +++ b/php/xmlrpc/config.m4 @@ -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=) + 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 index 00000000..ff54f895 --- /dev/null +++ b/php/xmlrpc/config.m4.lib64 @@ -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=) + 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 index 00000000..0f6bf0cd --- /dev/null +++ b/php/xmlrpc/config.w32 @@ -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 index 00000000..323edfa6 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/README @@ -0,0 +1,17 @@ +organization of this directory is moving towards this approach: + +.h -- public API and data types +_private.h -- protected API and data types +.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 index 00000000..49b6090f --- /dev/null +++ b/php/xmlrpc/libxmlrpc/acinclude.m4 @@ -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 index 00000000..b90536f0 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/base64.c @@ -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 + +/* ENCODE -- Encode binary file into base64. */ +#include +#include + +#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 index 00000000..4cf156ad --- /dev/null +++ b/php/xmlrpc/libxmlrpc/base64.h @@ -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 index 00000000..b7f7fcb4 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/encodings.c @@ -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 +#else +#include +#include +#endif + +static const char rcsid[] = "#(@) $Id: encodings.c 5574 2007-10-25 20:33:17Z thierry $"; + +#include + +#ifdef HAVE_GICONV_H +#include +#else +#include +#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 index 00000000..486360b1 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/encodings.h @@ -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 index 00000000..1bbf5a3c --- /dev/null +++ b/php/xmlrpc/libxmlrpc/queue.c @@ -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 +#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 index 00000000..be73f6da --- /dev/null +++ b/php/xmlrpc/libxmlrpc/queue.h @@ -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 index 00000000..a7dd7270 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/simplestring.c @@ -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 +#include +#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 index 00000000..c5d98cf1 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/simplestring.h @@ -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 index 00000000..c3c2b889 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/system_methods.c @@ -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 +#include +#include + + +static const char* xsm_introspection_xml = +"" + +"" + "" + + "" + "value identifier" + "value's xmlrpc or user-defined type" + "value's textual description " + "true if value is optional, else it is required " + "a child of this element. n/a for scalar types " + "" + + "" + "" + "" + + "" + "" + "" + + + "" + + "" + + "" + "" + "Dan Libby" + "fully describes the methods and types implemented by this XML-RPC server." + "1.1" + "" + "" + "" + "" + "a valid method name" + "" + "" + "" + "" + "" + "" + "method name" + "method version" + "method author" + "method purpose" + "" + "" + "parameter list" + "return value list" + "" + "" + "list of known bugs" + "list of possible errors and error codes" + "list of examples" + "list of modifications" + "list of notes" + "see also. list of related methods" + "list of unimplemented features" + "" + "" + "" + "a type description" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "Dan Libby" + "enumerates the methods implemented by this XML-RPC server." + "1.0" + "" + "" + "" + "" + "name of a method implemented by the server." + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "Dan Libby" + "provides documentation string for a single method" + "1.0" + "" + "" + "" + "name of the method for which documentation is desired" + "" + "" + "help text if defined for the method passed, otherwise an empty string" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "Dan Libby" + "provides 1 or more signatures for a single method" + "1.0" + "" + "" + "" + "name of the method for which documentation is desired" + "" + "" + "" + "" + "a string indicating the xmlrpc type of a value. one of: string, int, double, base64, datetime, array, struct" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "Dan Libby" + "executes multiple methods in sequence and returns the results" + "1.0" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "" + "" + "Dan Libby" + "returns a list of capabilities supported by this server" + "1.0" + "spec url: http://groups.yahoo.com/group/xml-rpc/message/2897" + "" + "" + "" + "" + "" + "www address of the specification defining this capability" + "version of the spec that this server's implementation conforms to" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + + "" +""; + + +/* 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 index 00000000..72408fd3 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/system_methods_private.h @@ -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 index 00000000..904ba157 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_element.c @@ -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 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 +#include +#include + +#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 "" +#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_END_LEN sizeof(END_TOKEN_END) - 1 +#define ATTR_DELIMITER "\"" +#define ATTR_DELIMITER_LEN sizeof(ATTR_DELIMITER) - 1 +#define CDATA_BEGIN "" +#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: */ + 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 index 00000000..a90b2d29 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_element.c.gcc4 @@ -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 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 +#include +#include + +#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 "" +#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_END_LEN sizeof(END_TOKEN_END) - 1 +#define ATTR_DELIMITER "\"" +#define ATTR_DELIMITER_LEN sizeof(ATTR_DELIMITER) - 1 +#define CDATA_BEGIN "" +#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: */ + 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 index 00000000..cfe7ca24 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_element.h @@ -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 +#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; /* " ?> */ +} 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 + * 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 index 00000000..b51d9917 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_to_dandarpc.c @@ -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 +#include +#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 index 00000000..6facb557 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_to_dandarpc.h @@ -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 index 00000000..8390f06e --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_to_soap.c @@ -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 +#include +#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 index 00000000..9ae9308b --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_to_soap.h @@ -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 index 00000000..2219bca3 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_to_xmlrpc.c @@ -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 +#include +#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(¶m->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 index 00000000..234a1534 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xml_to_xmlrpc.h @@ -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 index 00000000..9aca12f1 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc.c @@ -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 +#include +#include +#include +#include +#include + +#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 method name 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 ... 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 "" 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 "" element in that + * serialization. There will simply be a standard struct with 2 child elements. + * imho, the "" 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 index 00000000..eead11c4 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc.h @@ -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 /* 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 index 00000000..87da92db --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc.m4 @@ -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 index 00000000..589ff8eb --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc_introspection.c @@ -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 +#include +#include + + +/* 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 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 + * 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 index 00000000..656e441b --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc_introspection.h @@ -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 _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 index 00000000..7b97fa7e --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc_introspection_private.h @@ -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 index 00000000..65c6b136 --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc_private.h @@ -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 index 00000000..58c54bbb --- /dev/null +++ b/php/xmlrpc/libxmlrpc/xmlrpc_win32.h @@ -0,0 +1,11 @@ +#ifndef _XMLRPC_WIN32_H +#define _XMLRPC_WIN32_H +/* just some things needed to compile win32 */ +#include +#include +#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 index 00000000..24025d2a --- /dev/null +++ b/php/xmlrpc/php_xmlrpc.h @@ -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 index 00000000..eb86a987 --- /dev/null +++ b/php/xmlrpc/xmlrpc-epi-php.c @@ -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 index 00000000..8c455d3f --- /dev/null +++ b/php/xmlrpc/xmlrpc.dsp @@ -0,0 +1,211 @@ +# Microsoft Developer Studio Project File - Name="xmlrpc" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=xmlrpc - Win32 Debug_TS +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "xmlrpc.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "xmlrpc.mak" CFG="xmlrpc - Win32 Debug_TS" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "xmlrpc - Win32 Debug_TS" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "xmlrpc - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "xmlrpc - Win32 Debug_TS" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_TS" +# PROP BASE Intermediate_Dir "Debug_TS" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_TS" +# PROP Intermediate_Dir "Debug_TS" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# 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 +# 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 +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x1009 /d "_DEBUG" +# ADD RSC /l 0x1009 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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 +# 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" + +!ELSEIF "$(CFG)" == "xmlrpc - Win32 Release_TS" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release_TS" +# PROP BASE Intermediate_Dir "Release_TS" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release_TS" +# PROP Intermediate_Dir "Release_TS" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "XMLRPC_EXPORTS" /YX /FD /c +# 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 +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x1009 /d "NDEBUG" +# ADD RSC /l 0x1009 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# 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 +# 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" + +!ENDIF + +# Begin Target + +# Name "xmlrpc - Win32 Debug_TS" +# Name "xmlrpc - Win32 Release_TS" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=".\xmlrpc-epi-php.c" +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\php_xmlrpc.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# Begin Group "libxmlrpc" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\libxmlrpc\base64.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\base64.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\encodings.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\encodings.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\queue.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\queue.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\simplestring.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\simplestring.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\system_methods.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\system_methods_private.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_element.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_element.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_to_dandarpc.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_to_dandarpc.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_to_soap.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_to_soap.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_to_xmlrpc.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xml_to_xmlrpc.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xmlrpc.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xmlrpc.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xmlrpc_introspection.c +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xmlrpc_introspection.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xmlrpc_introspection_private.h +# End Source File +# Begin Source File + +SOURCE=.\libxmlrpc\xmlrpc_private.h +# End Source File +# End Group +# End Target +# End Project diff --git a/psycopg2/AUTHORS b/psycopg2/AUTHORS new file mode 100644 index 00000000..44c77fc8 --- /dev/null +++ b/psycopg2/AUTHORS @@ -0,0 +1,8 @@ +Main authors: + Federico Di Gregorio + +For the win32 port: + Jason Erickson (most of his changes are still in 2.0) + +Additional Help: + diff --git a/psycopg2/LICENSE b/psycopg2/LICENSE new file mode 100644 index 00000000..b20b2825 --- /dev/null +++ b/psycopg2/LICENSE @@ -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 index 00000000..457004c5 --- /dev/null +++ b/psycopg2/MANIFEST.in @@ -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 index 00000000..dadfc1b4 --- /dev/null +++ b/psycopg2/doc/ChangeLog-1.x @@ -0,0 +1,1744 @@ +2003-07-26 Federico Di Gregorio + + * 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 + + * cursor.c: applied notify and fileno patch from Vsevolod Lobko. + +2003-07-20 Federico Di Gregorio + + * 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 + + * 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 + + * Added python-taylor.txt in doc directory: very nice introduction + to DBAPI programming by Richard Taylor. + +2003-07-09 Federico Di Gregorio + + * 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 + + * Release 1.1.5.1. + + * ZPsycopgDA/db.py (DB.query): stupid error making ZPsycopgDA + unusable fixed (else->except). + +2003-06-22 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * module.h (PyObject_TypeCheck): fixed leak in python 2.1 + (Guido van Rossum). + +2003-04-08 Federico Di Gregorio + + * buildtypes.py (basic_types): removed LXTEXT (never user, does + not exists anymore.) + +2003-04-07 Federico Di Gregorio + + * setup.py: added very lame setup.py script. + +2003-04-02 Federico Di Gregorio + + * 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 + + * cursor.c (_psyco_curs_execute): psycopg was reporting everything + as IntegrityError; reported and fix suggested by Amin Abdulghani. + +2003-03-21 Federico Di Gregorio + + * cursor.c (psyco_curs_fetchone): debug statements sometimes made + psycopg segfault: fixed by a patch by Ken Simpson. + +2003-03-18 Federico Di Gregorio + + * cursor.c (alloc_keeper): patch from Dieter Maurer to unlock GIL + whaile calling PQconnectdb(). + +2003-03-05 Federico Di Gregorio + + * Release 1.1.2. + + * Applied cygwin patch from Hajime Nakagami. + +2003-02-25 Federico Di Gregorio + + * 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 + + * Merged in changes from 1.0.15.1 (see below for merged + ChangeLog.) + +2003-02-14 Federico Di Gregorio + + * 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. Fixed. + +2003-02-13 Federico Di Gregorio + + * Release 1.0.15. + + * 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 + + * 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 + + * typeobj.c (psyco_INTERVAL_cast): patched again to take into + account leading zeroes. + +2003-02-02 Federico Di Gregorio + + * 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 + + * config.h.in: applied patch from Albert Chin-A-Young to fix + asprintf prototype. + +2003-01-29 Federico Di Gregorio + + * cursor.c (_mogrify_seq): fixed little refcount leak, as + suggested by Yves Bastide. + +2003-01-24 Federico Di Gregorio + + * 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 + + * cursor.c (psyco_curs_fetchone): fixed little memory leak + reported by Dieter Maurer. + +2003-01-20 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * cursor.c (_psyco_curs_execute): Added skeleton to support COPY + FROM/TO. + +2002-09-06 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * Release 1.0.11pre1. + +2002-08-06 Federico Di Gregorio + + * ZPsycopgDA/DA.py (cast_DateTime): patched as suggested by Tom + Jenkins; now it shouldwork with time zones too. + +2002-08-01 Federico Di Gregorio + + * ZPsycopgDA/DA.py (cast_DateTime): fixed problem with missing + AM/PM, as reported by Tom Jenkins. + +2002-07-23 Federico Di Gregorio + + * Fixed buglets reported by Mike Coleman. + +2002-07-22 Federico Di Gregorio + + * Release 1.0.10. + +2002-07-14 Federico Di Gregorio + + * 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 + + * 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 + + * typeobj.c (psyco_XXX_cast): fixed bug reported by multiple users + by appliying Matt patch. + +2002-06-30 Federico Di Gregorio + + * ZPsycopgDA/DA.py (Connection.set_type_casts): applied patch from + Tom Jenkins to parse dates with TZ. + +2002-06-20 Federico Di Gregorio + + * Preparing for release 1.0.9. + + * Makefile.pre.in (dist): now we really include psycopg.spec. + +2002-06-17 Federico Di Gregorio + + * ZPsycopgDA/db.py (_finish, _abort): fixed problem with + connection left in invalid state by applying Tom Jenkins patch. + +2002-06-06 Federico Di Gregorio + + * ZPsycopgDA/db.py (DB._abort): fixed exception raising after an + error in execute triggerer deletion of self.db. + +2002-05-16 Federico Di Gregorio + + * 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 + + * ZPsycopgDA/DA.py (cast_Time): applied 'seconds as a float' patch + from Jelle. + +2002-04-23 Federico Di Gregorio + + * 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 + + * 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 + + * Release 1.0.7.1. + + * configure.in: fixed little bug as reported by ron. + +2002-04-05 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * ZPsycopgDA/DA.py: added MessageDialog import suggested by + Guido. + +2002-03-07 Federico Di Gregorio + + * 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 + + * Release 1.0.5. + + * applied table browser patch from Andy Dustman. + +2002-02-26 Federico Di Gregorio + + * 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 + + * configure.in: Release 1.0.4. + +2002-02-12 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * FAQ: added. + +2002-01-16 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * cursor.c: fixed memory leak due to extra strdup (thanks + to Leonardo Rochael Almeida.) + +2001-11-14 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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, + + * Makefile.pre.in: fixed small problem with zcat on True64 + (thank you stefan.) + +2001-11-06 Federico Di Gregorio + + * ZPsycopgDA/db.py (DB.query): added fix for concurrent update + from Chris Kratz. + +2001-11-05 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * module.h: ANSI C compatibility patch from Daniel Plagge. + +2001-10-30 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * typeobj.c (psyco_INTERVAL_cast): fixed bug caused by wrong + parsing on '1 day' (no hours, minutes and seconds.) + +2001-10-15 Michele Comitini + + * cursor.c (_execute): use the correct cast functions even on + retrival of binary cursors. + +2001-10-12 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * module.h: now debugging should be active only when asked by + ./configure --enable-devel + +2001-09-29 Federico Di Gregorio + + * cursor.c (new_psyco_cursobject): added locking of connection, + still unsure if necessary. + +2001-09-26 Federico Di Gregorio + + * 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 + + * ZPsycopgDA/db.py (DB._finish, DB._begin): fix for the + self.db == None problem. + +2001-09-19 Michele Comitini + + * 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 + + * typeobj.c (psyco_INTERVAL_cast): fixed interval conversion + (hours were incorrectly converted into seconds.) + +2001-09-17 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * doc/*.tex: added documentation directory and skeleton of the + psycopg guide. + +2001-09-03 Federico Di Gregorio + + * merged in changes from HEAD (mostly mcm fixes to binary + objects.) + + * preparing for release 0.99.6. + +2001-09-03 Michele Comitini + + * typemod.c: much faster Binary encoding routine. + + * typeobj.c: much faster Binary decoding routine. + +2001-08-28 Michele Comitini + + * 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 + + * cursor.c: fixed DATESTYLE problem thanx to Steve Drees. + +2001-07-26 Federico Di Gregorio + + * Makefile.pre.in: applied change suggested by Stefan H. Holek to + clobber and distclean targets. + +2001-07-23 Federico Di Gregorio + + * 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 + + * configure.in: Release 0.99.5. + +2001-07-12 Federico Di Gregorio + + * debian/* fixed some little packaging problems. + +2001-07-11 Federico Di Gregorio + + * cursor.c, typeobj.c: removed some Py_INCREF on PyDict_SetItem + keys and values to avoid memory leaks. + +2001-07-03 Federico Di Gregorio + + * 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 + + * 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 + + * ZPsycopgDA/db.py, ZPsycopgDA/DABase.py: applied patch sent by + yury to fix little buglet. + +2001-06-22 Federico Di Gregorio + + * 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 + + * 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 + + * ZPsycopgDA/__init__.py (initialize): applied patch from Jelle to + resolve problem with Zope 2.4.0a1. + +2001-06-14 Federico Di Gregorio + + * 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 + + * 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 + + * ZPsycopgDA/DA.py: DateTime casts simplified and corrected + as suggested by Yury. + +2001-06-05 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * Release 0.5.4. + +2001-05-17 Michele Comitini + + * 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 + + * Release 0.5.3. + + * Merged in changes from me and mcm. + +2001-05-06 Michele Comitini + + * 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 + + * ZPsycopgDA/db.py: added .close() method (as suffested by Andre + Schubert.) + +2001-05-04 Michele Comitini + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * Release 0.5.1. + + * mcm fixed a nasty bug by correcting a typo in module.h. + +2001-03-30 Federico Di Gregorio + + * 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 + + * 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 + + * cursor.c: added mutexes, they do not interact well with python + threads :(. + +2001-03-16 Michele Comitini + + * ZPsycopgDA/db.py (ZDA): some fixes in table browsing. + +2001-03-16 Federico Di Gregorio + + * 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 + + * connection.c (curs_commitall): splitted for cycle in two to + avoid the "bad snapshot" problem. + +2001-03-14 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * cursor.c (request_pgconn): added code to set datestyle to ISO on + new connections (many thanks to Yury for the code, + i changed it just a little bit to raise an exception on error.) + +2001-03-09 Federico Di Gregorio + + * Release 0.4.4. + + * ZPsycopgDA/db.py: michele fixed a nasty bug here. + +2001-03-08 Federico Di Gregorio + + * Release 0.4.3. + +2001-03-07 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * Release 0.4. + +2001-02-27 Michele Comitini + + * 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 + + * 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 + + * releasing 0.3 (added NEWS file.) + +2001-02-26 Michele Comitini + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * typeobj.c: separated type objects stuff from module.c + + * typeobj.h: separated type objects stuff from module.h + +2001-02-19 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * cursor.c: now executemany takes sequences and not just + tuples + +2001-02-07 Federico Di Gregorio + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 . + (Repository): Clean up and better naming for DBAPITypeObjects. + +2001-01-08 Michele Comitini + + * module.c (Repository): Corrected the exception hierarcy + + * connection.c (Repository): Begun to use the connection objects + of libpq + +2001-01-07 Michele Comitini + + * module.c (Repository): Added the Date/Time functions. + +2001-01-06 Michele Comitini + + * 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 + + * module.c (Repository): Test version; module loaded with + exception defined. + +2001-01-05 Michele Comitini + + * Setup.in (Repository): Setup file. + + * Makefile.pre.in (Repository): from the python source. + +2001-01-05 Michele Comitini + + * module.c: Written some code for defining exceptions. + + * module.h: Static variable for exceptions. + +2001-01-04 Michele Comitini + + * Changelog: pre-release just a few prototypes to get started. + + diff --git a/psycopg2/doc/SUCCESS b/psycopg2/doc/SUCCESS new file mode 100644 index 00000000..9ae91f58 --- /dev/null +++ b/psycopg2/doc/SUCCESS @@ -0,0 +1,114 @@ +From: Jack Moffitt +To: Psycopg Mailing List +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 +To: Psycopg Mailing List +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 +To: Federico Di Gregorio +Cc: Psycopg Mailing List +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 +To: Federico Di Gregorio +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 +To: Federico Di Gregorio +Cc: Psycopg Mailing List +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 +To: +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 +To: Michele Comitini +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 index 00000000..b20b2769 --- /dev/null +++ b/psycopg2/doc/TODO @@ -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 index 00000000..22e8f7ed --- /dev/null +++ b/psycopg2/doc/api-screen.css @@ -0,0 +1,138 @@ +/* Based on the Epydoc "default.css" +** with some missing reST-related classes +** and Python syntax support (from SilverCity) +*/ + +/* Body color */ +body { background: #ffffff; color: #000000; } + +/* Tables */ +table.summary, table.details, table.index + { background: #e8f0f8; color: #000000; } +tr.summary, tr.details, tr.index + { background: #70b0f0; color: #000000; + text-align: left; font-size: 120%; } +tr.group { background: #c0e0f8; color: #000000; + text-align: left; font-size: 120%; + font-style: italic; } + +/* Documentation page titles */ +h2.module { margin-top: 0.2em; } +h2.class { margin-top: 0.2em; } + +/* Headings */ +h1.heading { font-size: +140%; font-style: italic; + font-weight: bold; } +h2.heading { font-size: +125%; font-style: italic; + font-weight: bold; } +h3.heading { font-size: +110%; font-style: italic; + font-weight: normal; } + +/* Base tree */ +pre.base-tree { font-size: 80%; margin: 0; } + +/* TOC */ +p.toc { margin: 0; } + +/* Details Sections */ +table.func-details { background: #e8f0f8; color: #000000; + border: 2px groove #c0d0d0; + padding: 0 1em 0 1em; margin: 0.4em 0 0 0; } +h3.func-detail { background: transparent; color: #000000; + margin: 0 0 1em 0; } + +table.var-details { background: #e8f0f8; color: #000000; + border: 2px groove #c0d0d0; + padding: 0 1em 0 1em; margin: 0.4em 0 0 0; } +h3.var-details { background: transparent; color: #000000; + margin: 0 0 1em 0; } + +/* Function signatures */ +.sig { background: transparent; color: #000000; + font-weight: bold; } +.sig-name { background: transparent; color: #006080; } +.sig-arg, .sig-kwarg, .sig-vararg + { background: transparent; color: #008060; } +.sig-default { background: transparent; color: #602000; } +.summary-sig { background: transparent; color: #000000; } +.summary-sig-name { background: transparent; color: #204080; } +.summary-sig-arg, .summary-sig-kwarg, .summary-sig-vararg + { background: transparent; color: #008060; } + +/* Doctest blocks */ +.py-src { background: transparent; color: #000000; } +.py-prompt { background: transparent; color: #005050; + font-weight: bold;} +.py-string { background: transparent; color: #006030; } +.py-comment { background: transparent; color: #003060; } +.py-keyword { background: transparent; color: #600000; } +.py-output { background: transparent; color: #404040; } +div.code-block, +pre.literal-block, +pre.doctestblock { background: #f4faff; color: #000000; + padding: .5em; margin: 1em; + border: 1px solid #708890; } +table pre.doctestblock + { background: #dce4ec; color: #000000; + padding: .5em; margin: 1em; + border: 1px solid #708890; } +div.code-block { font-family: monospace; } + +/* Variable values */ +pre.variable { background: #dce4ec; color: #000000; + padding: .5em; margin: 0; + border: 1px solid #708890; } +.variable-linewrap { background: transparent; color: #604000; } +.variable-ellipsis { background: transparent; color: #604000; } +.variable-quote { background: transparent; color: #604000; } +.re { background: transparent; color: #000000; } +.re-char { background: transparent; color: #006030; } +.re-op { background: transparent; color: #600000; } +.re-group { background: transparent; color: #003060; } +.re-ref { background: transparent; color: #404040; } + +/* Navigation bar */ +table.navbar { background: #a0c0ff; color: #0000ff; + border: 2px groove #c0d0d0; } +th.navbar { background: #a0c0ff; color: #0000ff; } +th.navselect { background: #70b0ff; color: #000000; } +.nomargin { margin: 0; } + +/* Links */ +a:link { background: transparent; color: #0000ff; } +a:visited { background: transparent; color: #204080; } +a.navbar:link { background: transparent; color: #0000ff; + text-decoration: none; } +a.navbar:visited { background: transparent; color: #204080; + text-decoration: none; } + +/* Admonitions */ +div.warning, +div.note { background-color: #c0e0f8; + border: thin solid black; + padding: 1em; + margin-left: 1em; + margin-right: 1em; } +div.warning .first, +div.note .first { font-family: sans-serif; + font-size: 110%; + margin-right: 0.5em; } + +/* Lists */ +ul { margin-top: 0; } + +/* Python syntax */ +.p_character { color: olive; } +.p_classname { color: blue; font-weight: bold; } +.p_commentblock {color: gray; font-style: italic; } +.p_commentline { color: green; font-style: italic; } +.p_default {} +.p_defname { color: #009999; font-weight: bold; } +.p_identifier { color: black; } +.p_number { color: #009999; } +.p_operator { color: black; } +.p_string { color: #7F007F; } +.p_stringeol { color: #7F007F; } +.p_triple { color: #7F0000; } +.p_tripledouble { color: #7F0000; } +.p_word { color: navy; font-weight: bold; } diff --git a/psycopg2/doc/async.txt b/psycopg2/doc/async.txt new file mode 100644 index 00000000..518d5fe2 --- /dev/null +++ b/psycopg2/doc/async.txt @@ -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 index 00000000..cb712004 --- /dev/null +++ b/psycopg2/doc/extensions.html @@ -0,0 +1,219 @@ + + + + + + +psycopg 2 extensions to the DBAPI 2.0 + + + +
+

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.

+

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 a . cursor is much +more interesting, because it is the class where query building, execution and +result type-casting into Python variables happens.

+ + +
+
+

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`
+
+

System Message: ERROR/3 (../doc/extensions.rst, line 54); backlink

+Can't find 'ISOLATION_LEVEL_READ_COMMITTED' in any provided module.
+

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.
+
+
+
+

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));
+
+
+
+
+

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"
+
+
+ + +
+

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.

+
+
+

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/psycopg2/doc/extensions.rst b/psycopg2/doc/extensions.rst new file mode 100644 index 00000000..3bdc6807 --- /dev/null +++ b/psycopg2/doc/extensions.rst @@ -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: " 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 index 00000000..99dce334 --- /dev/null +++ b/pycurl/COPYING @@ -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. + + 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. + + 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. + + 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. + + 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. + + 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. + + 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. + + 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. + + 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 + + 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. + + + Copyright (C) + + 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. + + , 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 index 00000000..0e5ae7ed --- /dev/null +++ b/pycurl/ChangeLog @@ -0,0 +1,759 @@ +Version 7.13.1 [requires libcurl-7.13.1 or better] +-------------- + +2005-03-04 Kjetil Jacobsen + + * Use METH_NOARGS where appropriate. + +2005-03-03 Kjetil Jacobsen + + * 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 + + * 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 + + * 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 + + * 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 + + * Added CURLOPT_FTPSSLAUTH (and CURLFTPAUTH_*). + + * Added CURLINFO_OS_ERRNO. + +2004-08-17 Kjetil Jacobsen + + * Use LONG_LONG instead of PY_LONG_LONG to make pycurl compile + on Python versions < 2.3 (fix from Domenico Andreoli + ). + + +Version 7.12.1 +-------------- + +2004-08-02 Kjetil Jacobsen + + * Added INFOTYPE_SSL_DATA_IN/OUT. + +2004-07-16 Markus F.X.J. Oberhumer + + * WARNING: removed deprecated PROXY_, TIMECOND_ and non-prefixed + INFOTYPE constant names. See ChangeLog entry 2003-06-10. + +2004-06-21 Kjetil Jacobsen + + * 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 + + * 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 + + * 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 + + * 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 + + * 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 + + * Added support for libcurl 7.11.2 CURLOPT features: + CURLOPT_TCP_NODELAY. + +2004-03-25 Kjetil Jacobsen + + * 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 + + * WARNING: Removed support for the PASSWDFUNCTION callback, which + is no longer supported by libcurl. + +2004-03-15 Kjetil Jacobsen + + * Added support for libcurl 7.11.1 CURLOPT features: + CURLOPT_POSTFIELDSIZE_LARGE. + + +Version 7.11.0 +-------------- + +2004-02-11 Kjetil Jacobsen + + * 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 + + * 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 + + * 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 + + * Added missing CURLOPT_ENCODING option (patch by Martijn + Boerwinkel ) + + +Version 7.10.6 +-------------- + +2003-07-29 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Added support for CURLOPT_FTP_USE_EPRT (libcurl-7.10.5). + + * Documentation updates. + +2003-05-07 Eric S. Raymond + + * Lifted all HTML docs to clean XHTML, verified by tidy. + +2003-05-02 Markus F.X.J. Oberhumer + + * Fixed some `int' vs. `long' mismatches that affected 64-bit systems. + + * Fixed wrong pycurl.CAPATH constant. + +2003-05-01 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Markus added CURLOPT_UNRESTRICTED_AUTH (libcurl-7.10.4). + +2003-02-25 Kjetil Jacobsen + + * Fixed some broken test code and removed the fileupload test + since it didn't work properly. + +2003-01-28 Kjetil Jacobsen + + * Some documentation updates by Markus and me. + +2003-01-22 Kjetil Jacobsen + + * 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 + + * PycURL memory usage has been reduced. + +2003-01-10 Kjetil Jacobsen + + * Added 'examples/retriever-multi.py' which shows how to retrieve + a set of URLs concurrently using the multi interface. + +2003-01-09 Kjetil Jacobsen + + * Added support for CURLOPT_HTTP200ALIASES. + +2002-11-22 Kjetil Jacobsen + + * Updated pycurl documentation in the 'doc' directory. + +2002-11-21 Kjetil Jacobsen + + * 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 + + * Added new method CurlMulti.info_read(). + + +Version 7.10.2 +-------------- + +2002-11-14 Kjetil Jacobsen + + * 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 + . + +2002-11-06 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Added new module-level function version_info() from + libcurl-7.10. + + +Version 7.10 +------------ + +2002-09-13 Kjetil Jacobsen + + * 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 + + * 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 + + * 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 + + * Added support for curl_multi_fdset. See the file + 'tests/test_multi4.py' for example usage. Contributed by Conrad + Steenberg . + + * perform() on multi objects now returns a tuple (result, number + of handles) like the libcurl interface does. + +2002-08-08 Kjetil Jacobsen + + * 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 . + +2002-07-20 Markus F.X.J. Oberhumer + + * 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 + + * 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 + + * Curl and CurlMulti objects now support standard Python attributes. + See tests/test_multi2.py for an example. + +2002-07-02 Kjetil Jacobsen + + * Added support for the multi-interface. + + +Version 7.9.8.1 +--------------- + +2002-06-25 Markus F.X.J. Oberhumer + + * Fixed a couple of `int' vs. `size_t' mismatches in callbacks + and Py_BuildValue() calls. + +2002-06-25 Kjetil Jacobsen + + * Use 'double' type instead of 'size_t' for progress callbacks + (by Conrad Steenberg ). Also cleaned up + some other type mismatches in the callback interfaces. + +2002-06-24 Kjetil Jacobsen + + * Added example code on how to upload a file using HTTPPOST in + pycurl (code by Amit Mongia ). See the + file 'test_fileupload.py' for details. + + +Version 7.9.8 +------------- + +2002-06-24 Kjetil Jacobsen + + * Resolved some build problems on Windows (by Markus Oberhumer). + +2002-06-19 Kjetil Jacobsen + + * 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 + + * Made some changes to setup.py which should fix the build + problems on RedHat 7.3 (suggested by Benji ). + + * 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 + + * 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 + + * Added CURLINFO_REDIRECT_TIME and CURLINFO_REDIRECT_COUNT. + +2002-04-27 Kjetil Jacobsen + + * Fixed potential memory leak and thread race (by Markus + Oberhumer). + + +Version 0.4.9 +------------- + +2002-04-15 Kjetil Jacobsen + + * 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 + + * Added 'test_post2.py' file which is another example on how to + issue POST requests. + +2002-04-11 Markus F.X.J. Oberhumer + + * Added the 'test_post.py' file which demonstrates the use of + POST requests. + + +Version 0.4.8 +------------- + +2002-03-07 Kjetil Jacobsen + + * Added CURLOPT_PREQUOTE. + + * Now requires libcurl 7.9.5 or greater. + + * Other minor code cleanups and bugfixes. + +2002-03-05 Kjetil Jacobsen + + * Do not allow WRITEFUNCTION and WRITEHEADER on the same handle. + + +Version 0.4.7 +------------- + +2002-02-27 Kjetil Jacobsen + + * 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 + + * Clarence Garnder found a bug where string + arguments to setopt sometimes were prematurely deallocated, this + should now be fixed. + +2002-02-21 Kjetil Jacobsen + + * 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 + + * Fixed docstring for getopt() function. + +2001-12-18 Kjetil Jacobsen + + * Updated the INSTALL information for Win32. + +2001-12-12 Kjetil Jacobsen + + * Added missing link flag to make pycurl build on MacOS X (by Matt + King ). + +2001-12-06 Kjetil Jacobsen + + * Added CURLINFO_STARTTRANSFER_TIME and CURLOPT_FTP_USE_EPSV from + libcurl 7.9.2. + +2001-12-01 Markus F.X.J. Oberhumer + + * Added the 'test_stringio.py' file which demonstrates the use of + StringIO objects as callback. + +2001-12-01 Markus F.X.J. Oberhumer + + * setup.py: Do not remove entries from a list while iterating + over it. + +2001-11-29 Kjetil Jacobsen + + * Added code in setup.py to install on Windows. Requires some + manual configuration (by Tino Lange ). + +2001-11-27 Kjetil Jacobsen + + * 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 + + * Some of the newer options to setopt were missing, this should + now be fixed. + +2001-11-04 Kjetil Jacobsen + + * Exception handling has been improved and should no longer throw + spurious exceptions (by Markus F.X.J. Oberhumer + ). + +2001-10-15 Kjetil Jacobsen + + * Refactored the test_gtk.py script to avoid global variables. + +2001-10-12 Kjetil Jacobsen + + * 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 ). + + * Allow C-functions and Python methods as callbacks (by Markus + F.X.J. Oberhumer ). + + * Allow None as success result of write, header and progress + callback invocations (by Markus F.X.J. Oberhumer + ). + + * 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 + + * Cleaned up the script with GNOME/PycURL integration. + +2001-08-20 Kjetil Jacobsen + + * 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 + + * Added test script for using PycURL and GNOME (tests/test_gtk.py). + +2001-08-20 Kjetil Jacobsen + + * Added test script for using XML-RPC (tests/test_xmlrpc.py). + + * Added more comments to the test sources. + +2001-08-06 Kjetil Jacobsen + + * Renamed module namespace to pycurl instead of curl. + +2001-08-06 Kjetil Jacobsen + + * Set CURLOPT_VERBOSE to 0 by default. + +2001-06-29 Kjetil Jacobsen + + * Updated INSTALL, curl version 7.8 or greater is now mandatory to + use pycurl. + +2001-06-13 Kjetil Jacobsen + + * Set NOPROGRESS to 1 by default. + +2001-06-07 Kjetil Jacobsen + + * Added global_init/cleanup. + +2001-06-06 Kjetil Jacobsen + + * Added HEADER/PROGRESSFUNCTION callbacks (see files in tests/). + + * Added PASSWDFUNCTION callback (untested). + + * Added READFUNCTION callback (untested). + +2001-06-05 Kjetil Jacobsen + + * 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 + + * Return -1 on error from Python callback in WRITEFUNCTION callback. + +2001-06-01 Kjetil Jacobsen + + * Moved source to src and tests to tests directory. + +2001-05-31 Kjetil Jacobsen + + * Added better type checking for setopt. + +2001-05-30 Kjetil Jacobsen + + * Moved code to sourceforge. + + * Added getinfo support. + + +# vi:ts=8:et diff --git a/pycurl/INSTALL b/pycurl/INSTALL new file mode 100644 index 00000000..ef42cf62 --- /dev/null +++ b/pycurl/INSTALL @@ -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 index 00000000..f4e3837b --- /dev/null +++ b/pycurl/MANIFEST.in @@ -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 index 00000000..9b2369d1 --- /dev/null +++ b/pycurl/Makefile @@ -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 index 00000000..f0c0e974 --- /dev/null +++ b/pycurl/PKG-INFO @@ -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 index 00000000..bd04ab67 --- /dev/null +++ b/pycurl/README @@ -0,0 +1,12 @@ +LICENSE +------- + +Copyright (C) 2001-2005 by Kjetil Jacobsen +Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer + +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 index 00000000..7541535d --- /dev/null +++ b/pycurl/TODO @@ -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 index 00000000..c8086468 --- /dev/null +++ b/pycurl/doc/callbacks.html @@ -0,0 +1,140 @@ + + + + + PyCurl: Callbacks + + + + + + +

Callbacks

+ +

For more fine-grained control, libcurl allows a +number of callbacks to be associated with each connection. In +pycurl, callbacks are defined using the setopt() 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.

+ +

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. +

+ +The signature of each callback used in pycurl is as follows:
+
+WRITEFUNCTION(string) -> number of characters written
+
+
+READFUNCTION(number of characters to read)-> +string
+
+HEADERFUNCTION(string) -> number of characters written
+

+PROGRESSFUNCTION(download total, downloaded, upload total, uploaded) -> status
+
+DEBUGFUNCTION(debug message type, debug message string) +-> None
+
+IOCTLFUNCTION(ioctl cmd) +-> status
+
+
+ +

Example: Callbacks for document header and body

+ +

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.

+ +
+    ## 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()
+
+ +

Example: Download/upload progress callback

+ +

This example shows how to use the progress callback. When downloading +a document, the arguments related to uploads are zero, and vice versa.

+ +
+    ## 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()
+
+ +

Example: Debug callbacks

+ +

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.

+ +
+    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()
+
+ +

Other examples

+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.

+ + +
+

+ Valid XHTML 1.0! + $Id: callbacks.html 5574 2007-10-25 20:33:17Z thierry $ +

+ + + diff --git a/pycurl/doc/curlmultiobject.html b/pycurl/doc/curlmultiobject.html new file mode 100644 index 00000000..7af3d544 --- /dev/null +++ b/pycurl/doc/curlmultiobject.html @@ -0,0 +1,136 @@ + + + + + PycURL: CurlMulti Objects + + + + + + +

CurlMulti Object

+ +

CurlMulti objects have the following methods:

+ +
+
close() -> None
+
+

Corresponds to +curl_multi_cleanup() 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.

+
+ +
perform() -> tuple of status and the number of active Curl objects
+
+

Corresponds to +curl_multi_perform() in libcurl.

+
+ +
add_handle(Curl object) -> None
+
+

Corresponds to +curl_multi_add_handle() in libcurl. +This method adds an existing and valid Curl object to the CurlMulti +object.

+ +

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).

+
+ +
remove_handle(Curl object) -> None
+
+

Corresponds to +curl_multi_remove_handle() in libcurl. +This method removes an existing and valid Curl object from the CurlMulti +object.

+ +

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).

+
+ +
fdset() -> +triple of lists with active file descriptors, +readable, writeable, exceptions.
+
+

Corresponds to +curl_multi_fdset() in libcurl. +This method extracts the file descriptor information from a CurlMulti object. +The returned lists can be used with the select module to +poll for events.

+ +

Example usage:

+ +
+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
+
+
+ +
select([timeout]) -> +number of ready file descriptors or -1 on timeout
+
+

This is a convenience function which simplifies the combined +use of fdset() and the select module.

+ +

Example usage:

+ +
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
+
+
+ +
info_read([max]) -> +numberof queued messages, a list of successful objects, a list of +failed objects
+
+

Corresponds to the +curl_multi_info_read() function in libcurl. +This method extracts at most max 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 <curl object, curl error number, curl +error message> for each failed curl object. The number +of queued messages after this method has been called is also +returned.

+
+
+ +
+

+ Valid XHTML 1.0! + $Id: curlmultiobject.html 5574 2007-10-25 20:33:17Z thierry $ +

+ + + diff --git a/pycurl/doc/curlobject.html b/pycurl/doc/curlobject.html new file mode 100644 index 00000000..b6ad7740 --- /dev/null +++ b/pycurl/doc/curlobject.html @@ -0,0 +1,102 @@ + + + + + PycURL: Curl Objects + + + + + + +

Curl Object

+ +

Curl objects have the following methods:

+ +
+
close() -> None
+
+

Corresponds to +curl_easy_cleanup 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.

+
+ +
perform() -> None
+
+

Corresponds to +curl_easy_perform in libcurl.

+
+ +
setopt(option, value) -> None
+
+ +

Corresponds to +curl_easy_setopt in libcurl, where +option is specified with the CURLOPT_* constants in libcurl, +except that the CURLOPT_ prefix has been removed. The type for +value depends on the option, and can be either a string, +integer, long integer, file objects, lists, or functions.

+ +

Example usage:

+ +
+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()
+...
+
+
+ +
getinfo(option) -> Result
+
+ +

Corresponds to +curl_easy_getinfo in libcurl, where +option is the same as the CURLINFO_* constants in libcurl, +except that the CURLINFO_ prefix has been removed. +Result contains an integer, float or string, depending on +which option is given. The getinfo method should +not be called unless perform has been called and +finished.

+ +

Example usage:

+ +
+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)
+...
+--> 200 "http://sourceforge.net/"
+
+
+ +
errstr() -> String
+
+

Returns the internal libcurl error buffer of this handle as a string.

+
+
+ + +
+

+ Valid XHTML 1.0! + $Id: curlobject.html 5574 2007-10-25 20:33:17Z thierry $ +

+ + + diff --git a/pycurl/doc/pycurl.html b/pycurl/doc/pycurl.html new file mode 100644 index 00000000..7d796d0e --- /dev/null +++ b/pycurl/doc/pycurl.html @@ -0,0 +1,120 @@ + + + + + PycURL Documentation + + + + + + +

pycurl — A Python interface to the cURL library

+ +

The pycurl package is a Python interface to libcurl (http://curl.haxx.se/libcurl/). pycurl +has been successfully built and tested with Python versions from +2.2 to the current 2.4.x releases.

+ +

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.

+ +

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 +(http://curl.haxx.se/libcurl/c/).

+ +
+ +

Module Functionality

+ +
+
pycurl.global_init(option) ->None
+ +

option is one of the constants +pycurl.GLOBAL_SSL, pycurl.GLOBAL_WIN32, pycurl.GLOBAL_ALL, +pycurl.GLOBAL_NOTHING, pycurl.GLOBAL_DEFAULT. Corresponds to +curl_global_init() in libcurl.

+
+ +
pycurl.global_cleanup() -> None
+
+

Corresponds to +curl_global_cleanup() in libcurl.

+
+ +
pycurl.version
+ +

This is a string with version information on libcurl, +corresponding to +curl_version() in libcurl.

+ +

Example usage:

+
+>>> import pycurl
+>>> pycurl.version
+'libcurl/7.12.3 OpenSSL/0.9.7e zlib/1.2.2.1 libidn/0.5.12'
+
+
+ +
pycurl.version_info() -> Tuple
+
+

Corresponds to +curl_version_info() in libcurl. +Returns a tuple of information which is similar to the +curl_version_info_data struct returned by +curl_version_info() in libcurl.

+ +

Example usage:

+
+>>> 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')
+
+
+ +
pycurl.Curl() -> Curl object
+
+

This function creates a new +Curl object which corresponds to a +CURL 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.

+
+ +
pycurl.CurlMulti() -> CurlMulti object
+
+

This function creates a new +CurlMulti object which corresponds to +a CURLM handle in libcurl.

+
+
+ +
+ +

Subsections

+ + + +
+

+ Valid XHTML 1.0! + $Id: pycurl.html 5574 2007-10-25 20:33:17Z thierry $ +

+ + + diff --git a/pycurl/examples/basicfirst.py b/pycurl/examples/basicfirst.py new file mode 100644 index 00000000..5bed8b50 --- /dev/null +++ b/pycurl/examples/basicfirst.py @@ -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 index 00000000..2734d5ab --- /dev/null +++ b/pycurl/examples/file_upload.py @@ -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 " % 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 index 00000000..a60eba16 --- /dev/null +++ b/pycurl/examples/linksys.py @@ -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 []" + 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 " + print "Sets the Host field to be queried by the ISP." + + def do_domain(self, line): + print "Usage: host " + 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 " + 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 " + 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 " + 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 " + 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 " + 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 " + 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 " + 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} " + 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 " + 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 " + 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 " + 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 " + 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} " + 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 " + 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 index 00000000..b8f3629f --- /dev/null +++ b/pycurl/examples/retriever-multi.py @@ -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 [<# 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 [<# 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 index 00000000..422a9e58 --- /dev/null +++ b/pycurl/examples/retriever.py @@ -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 [<# 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 [<# 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 index 00000000..0c63f613 --- /dev/null +++ b/pycurl/examples/sfquery.py @@ -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 index 00000000..28950d84 --- /dev/null +++ b/pycurl/examples/xmlrpc_curl.py @@ -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 index 00000000..8fecb4d8 --- /dev/null +++ b/pycurl/python/curl/__init__.py @@ -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 index 00000000..8c7b68b8 --- /dev/null +++ b/pycurl/setup.py @@ -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 index 00000000..96072a80 --- /dev/null +++ b/pycurl/setup_win32_ssl.py @@ -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 index 00000000..a4828d3e --- /dev/null +++ b/pycurl/src/Makefile @@ -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 index 00000000..e7a31397 --- /dev/null +++ b/pycurl/src/pycurl.c @@ -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 + * Copyright (C) 2001-2005 by Markus F.X.J. Oberhumer + * + * Contributions: + * Tino Lange + * Matt King + * Conrad Steenberg + * Amit Mongia + * Eric S. Raymond + * Martin Muenstermann + * Domenico Andreoli + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#undef NDEBUG +#include + +/* 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 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 + **/ + + /* 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 */ + 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 + **/ + + /* 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 index 00000000..de787a66 --- /dev/null +++ b/pycurl/tests/test.py @@ -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 " % 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 index 00000000..936293df --- /dev/null +++ b/pycurl/tests/test_cb.py @@ -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 index 00000000..07bec020 --- /dev/null +++ b/pycurl/tests/test_debug.py @@ -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 index 00000000..927048b2 --- /dev/null +++ b/pycurl/tests/test_getinfo.py @@ -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 index 00000000..1b306f68 --- /dev/null +++ b/pycurl/tests/test_gtk.py @@ -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 " % 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 index 00000000..6a6c02fa --- /dev/null +++ b/pycurl/tests/test_internals.py @@ -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 index 00000000..a43ef060 --- /dev/null +++ b/pycurl/tests/test_memleak.py @@ -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 index 00000000..0befd4e8 --- /dev/null +++ b/pycurl/tests/test_multi.py @@ -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 index 00000000..6da93c25 --- /dev/null +++ b/pycurl/tests/test_multi2.py @@ -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 index 00000000..8889246b --- /dev/null +++ b/pycurl/tests/test_multi3.py @@ -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 index 00000000..5ab5be86 --- /dev/null +++ b/pycurl/tests/test_multi4.py @@ -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 index 00000000..0ddb5188 --- /dev/null +++ b/pycurl/tests/test_multi5.py @@ -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 index 00000000..714c5c87 --- /dev/null +++ b/pycurl/tests/test_multi6.py @@ -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 index 00000000..8dac8064 --- /dev/null +++ b/pycurl/tests/test_multi_vs_thread.py @@ -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 index 00000000..17caff61 --- /dev/null +++ b/pycurl/tests/test_post.py @@ -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 index 00000000..60f02fa9 --- /dev/null +++ b/pycurl/tests/test_post2.py @@ -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 index 00000000..8a9ea931 --- /dev/null +++ b/pycurl/tests/test_post3.py @@ -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 index 00000000..21a3153e --- /dev/null +++ b/pycurl/tests/test_stringio.py @@ -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 index 00000000..918f76fd --- /dev/null +++ b/pycurl/tests/test_xmlrpc.py @@ -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 = """ +%s%s +""" + +# 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 index 00000000..e4e9607d --- /dev/null +++ b/pycurl/tests/util.py @@ -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 + + -- 2.47.0