merge upstream phpxmlrpc
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Thu, 28 Apr 2022 10:10:47 +0000 (12:10 +0200)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Thu, 28 Apr 2022 10:15:09 +0000 (12:15 +0200)
using command:
git subtree pull --prefix php/phpxmlrpc phpxmlrpc master

105 files changed:
php/phpxmlrpc/.editorconfig [new file with mode: 0644]
php/phpxmlrpc/.gitattributes [new file with mode: 0644]
php/phpxmlrpc/.github/workflows/ci.yml [new file with mode: 0644]
php/phpxmlrpc/.gitignore
php/phpxmlrpc/.travis.yml [deleted file]
php/phpxmlrpc/INSTALL.md
php/phpxmlrpc/NEWS
php/phpxmlrpc/README.md
php/phpxmlrpc/composer.json
php/phpxmlrpc/debugger/action.php
php/phpxmlrpc/debugger/common.php
php/phpxmlrpc/debugger/controller.php
php/phpxmlrpc/debugger/favicon.ico [new file with mode: 0644]
php/phpxmlrpc/debugger/index.php
php/phpxmlrpc/demo/client/_append.php [new file with mode: 0644]
php/phpxmlrpc/demo/client/_prepend.php [new file with mode: 0644]
php/phpxmlrpc/demo/client/agesort.php
php/phpxmlrpc/demo/client/getstatename.php
php/phpxmlrpc/demo/client/introspect.php
php/phpxmlrpc/demo/client/mail.php
php/phpxmlrpc/demo/client/perl/test.pl [new file with mode: 0644]
php/phpxmlrpc/demo/client/proxy.php
php/phpxmlrpc/demo/client/python/test.py [new file with mode: 0644]
php/phpxmlrpc/demo/client/which.php
php/phpxmlrpc/demo/client/wrap.php
php/phpxmlrpc/demo/server/_append.php [new file with mode: 0644]
php/phpxmlrpc/demo/server/_prepend.php [new file with mode: 0644]
php/phpxmlrpc/demo/server/discuss.php
php/phpxmlrpc/demo/server/methodProviders/functions.php [new file with mode: 0644]
php/phpxmlrpc/demo/server/methodProviders/interop.php [new file with mode: 0644]
php/phpxmlrpc/demo/server/methodProviders/validator1.php [new file with mode: 0644]
php/phpxmlrpc/demo/server/methodProviders/wrapper.php [new file with mode: 0644]
php/phpxmlrpc/demo/server/proxy.php
php/phpxmlrpc/demo/server/server.php
php/phpxmlrpc/demo/vardemo.php
php/phpxmlrpc/doc/ChangeLog [moved from php/phpxmlrpc/ChangeLog with 99% similarity]
php/phpxmlrpc/doc/build/composer.json [new file with mode: 0644]
php/phpxmlrpc/doc/build/setup_tools.sh [new file with mode: 0644]
php/phpxmlrpc/doc/manual/phpxmlrpc_manual.adoc
php/phpxmlrpc/extras/benchmark.php [moved from php/phpxmlrpc/tests/benchmark.php with 91% similarity]
php/phpxmlrpc/extras/rsakey.pem [deleted file]
php/phpxmlrpc/extras/test.pl [deleted file]
php/phpxmlrpc/extras/test.py [deleted file]
php/phpxmlrpc/extras/verify_compat.php [moved from php/phpxmlrpc/tests/verify_compat.php with 98% similarity]
php/phpxmlrpc/extras/workspace.testPhpServer.fttb [deleted file]
php/phpxmlrpc/lib/xmlrpc.inc
php/phpxmlrpc/lib/xmlrpc_wrappers.inc
php/phpxmlrpc/lib/xmlrpcs.inc
php/phpxmlrpc/pakefile.php
php/phpxmlrpc/phpunit.xml.dist [new file with mode: 0644]
php/phpxmlrpc/src/Client.php
php/phpxmlrpc/src/Encoder.php
php/phpxmlrpc/src/Exception/HttpException.php [new file with mode: 0644]
php/phpxmlrpc/src/Exception/PhpXmlrpcException.php [new file with mode: 0644]
php/phpxmlrpc/src/Helper/Charset.php
php/phpxmlrpc/src/Helper/Date.php
php/phpxmlrpc/src/Helper/Http.php
php/phpxmlrpc/src/Helper/Logger.php
php/phpxmlrpc/src/Helper/XMLParser.php
php/phpxmlrpc/src/PhpXmlRpc.php
php/phpxmlrpc/src/Request.php
php/phpxmlrpc/src/Response.php
php/phpxmlrpc/src/Server.php
php/phpxmlrpc/src/Value.php
php/phpxmlrpc/src/Wrapper.php
php/phpxmlrpc/tests/0CharsetTest.php
php/phpxmlrpc/tests/1ValueTest.php [new file with mode: 0644]
php/phpxmlrpc/tests/2MessageTest.php [moved from php/phpxmlrpc/tests/1ParsingBugsTest.php with 71% similarity]
php/phpxmlrpc/tests/3EncoderTest.php [new file with mode: 0644]
php/phpxmlrpc/tests/4ClientTest.php [moved from php/phpxmlrpc/tests/2InvalidHostTest.php with 76% similarity]
php/phpxmlrpc/tests/4LocalhostMultiTest.php [deleted file]
php/phpxmlrpc/tests/5ServerTest.php [moved from php/phpxmlrpc/tests/3LocalhostTest.php with 88% similarity]
php/phpxmlrpc/tests/6HTTPTest.php [new file with mode: 0644]
php/phpxmlrpc/tests/7DemofilesTest.php [moved from php/phpxmlrpc/tests/5DemofilesTest.php with 68% similarity]
php/phpxmlrpc/tests/7ExtraTest.php [deleted file]
php/phpxmlrpc/tests/8DebuggerTest.php [moved from php/phpxmlrpc/tests/6DebuggerTest.php with 52% similarity]
php/phpxmlrpc/tests/9ExtraFilesTest.php [new file with mode: 0644]
php/phpxmlrpc/tests/PolyfillTestCase.php [new file with mode: 0644]
php/phpxmlrpc/tests/PolyfillTestCase7.php [new file with mode: 0644]
php/phpxmlrpc/tests/PolyfillTestCase8.php [new file with mode: 0644]
php/phpxmlrpc/tests/WebTestCase.php [moved from php/phpxmlrpc/tests/LocalFileTestCase.php with 62% similarity]
php/phpxmlrpc/tests/ci/Dockerfile [new file with mode: 0644]
php/phpxmlrpc/tests/ci/config/apache_phpfpm_proxyfcgi [new file with mode: 0644]
php/phpxmlrpc/tests/ci/config/apache_vhost [new file with mode: 0644]
php/phpxmlrpc/tests/ci/config/codecoverage_xdebug.ini [new file with mode: 0644]
php/phpxmlrpc/tests/ci/config/privoxy [moved from php/phpxmlrpc/tests/ci/travis/privoxy with 100% similarity]
php/phpxmlrpc/tests/ci/docker/entrypoint.sh [new file with mode: 0644]
php/phpxmlrpc/tests/ci/setup/create_user.sh [new file with mode: 0755]
php/phpxmlrpc/tests/ci/setup/install_packages.sh [new file with mode: 0755]
php/phpxmlrpc/tests/ci/setup/setup_apache.sh [new file with mode: 0755]
php/phpxmlrpc/tests/ci/setup/setup_code_coverage.sh [new file with mode: 0755]
php/phpxmlrpc/tests/ci/setup/setup_composer.sh [new file with mode: 0755]
php/phpxmlrpc/tests/ci/setup/setup_perl.sh [new file with mode: 0644]
php/phpxmlrpc/tests/ci/setup/setup_php.sh [new file with mode: 0755]
php/phpxmlrpc/tests/ci/setup/setup_privoxy.sh [new file with mode: 0755]
php/phpxmlrpc/tests/ci/travis/apache_vhost [deleted file]
php/phpxmlrpc/tests/ci/travis/apache_vhost_hhvm [deleted file]
php/phpxmlrpc/tests/ci/travis/setup_apache.sh [deleted file]
php/phpxmlrpc/tests/ci/travis/setup_apache_hhvm.sh [deleted file]
php/phpxmlrpc/tests/ci/travis/setup_hhvm.sh [deleted file]
php/phpxmlrpc/tests/ci/travis/setup_php_fpm.sh [deleted file]
php/phpxmlrpc/tests/ci/travis/setup_privoxy.sh [deleted file]
php/phpxmlrpc/tests/ci/vm.sh [new file with mode: 0755]
php/phpxmlrpc/tests/parse_args.php
php/phpxmlrpc/tests/phpunit_coverage.php

diff --git a/php/phpxmlrpc/.editorconfig b/php/phpxmlrpc/.editorconfig
new file mode 100644 (file)
index 0000000..677e36e
--- /dev/null
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/php/phpxmlrpc/.gitattributes b/php/phpxmlrpc/.gitattributes
new file mode 100644 (file)
index 0000000..5b0bb2d
--- /dev/null
@@ -0,0 +1,13 @@
+*.inc  diff=php
+
+demo/ export-ignore
+doc/ export-ignore
+extras/ export-ignore
+
+.github/ export-ignore
+tests/ export-ignore
+.editorconfig export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+pakefile.php export-ignore
+phpunit.xml.dist export-ignore
diff --git a/php/phpxmlrpc/.github/workflows/ci.yml b/php/phpxmlrpc/.github/workflows/ci.yml
new file mode 100644 (file)
index 0000000..5d1f245
--- /dev/null
@@ -0,0 +1,98 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+    test:
+        runs-on: ${{ matrix.operating-system }}
+        env:
+            HTTPSERVER: localhost
+            URI: /demo/server/server.php
+            HTTPSSERVER: localhost
+            HTTPSURI: /demo/server/server.php
+            PROXYSERVER: localhost:8080
+            # @todo check: is this necessary as well on GHA runners?
+            # was: Travis currently compiles PHP with an oldish cURL/GnuTLS combination;
+            # to make the tests pass when Apache has a bogus SSL cert whe need the full set of options below.
+            HTTPSVERIFYHOST: 0
+            HTTPSIGNOREPEER: 1
+            SSLVERSION: 0
+            DEBUG: 0
+        strategy:
+            fail-fast: false
+            matrix:
+                # @see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
+                # @todo fix: atm our tests fail when using ubuntu 20 (focal) and php 5.6 - 7.1, when using
+                #       an ssl stream context for connecting to localhost via https
+                operating-system: ['ubuntu-18.04'] # @todo add 'windows-latest'
+                # @todo use an older version of phpunit to enable testing on php 5.3 - 5.5 . Also: we will most likely
+                #       have to resort to using shivammathur/setup-php@v2 instead of sury's ppa to get php installed
+                php: ['8.1', '8.0', '7.4', '7.3', '7.2', '7.1', '7.0', '5.6']
+        steps:
+            -
+                uses: actions/checkout@v2
+            # Although this action is useful, we prefer to use the same script to set up php that we use for the
+            # docker image used for local testing. This allows us to make sure that script is always in good shape
+            #-
+            #    uses: shivammathur/setup-php@v2
+            #    with:
+            #        php-version: ${{ matrix.php }}
+            #        extensions: curl, dom, mbstring, xsl
+            #        ini-values: 'cgi.fix_pathinfo=1, always_populate_raw_post_data=-1'
+            #        #tools: phpunit/phpunit:a_version_compatible_with_php_5.3-5.5
+            #        # NB: this disables xdebug completely
+            #        coverage: none
+            -
+                # @todo add env setup scripts for windows
+                run: |
+                    chmod 755 ./tests/ci/setup/*.sh
+                    sudo --preserve-env=GITHUB_ACTIONS ./tests/ci/setup/setup_perl.sh
+                    sudo --preserve-env=GITHUB_ACTIONS ./tests/ci/setup/setup_apache.sh
+                    sudo --preserve-env=GITHUB_ACTIONS ./tests/ci/setup/setup_privoxy.sh
+                    sudo --preserve-env=GITHUB_ACTIONS ./tests/ci/setup/setup_php.sh ${{ matrix.php }}
+                    sudo --preserve-env=GITHUB_ACTIONS ./tests/ci/setup/setup_composer.sh
+            # Avoid downloading composer deps on every workflow. Is this useful/working for us?
+            #-
+            #    uses: actions/cache@v2
+            #    with:
+            #        path: /tmp/composer-cache
+            #        key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
+            -
+                run: 'composer install'
+            -
+                if: ${{ matrix.php == '7.4' }}
+                run: |
+                    ./tests/ci/setup/setup_code_coverage.sh enable
+                    ./vendor/bin/phpunit -v --coverage-clover=coverage.clover tests
+                    if [ -f coverage.clover ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
+            -
+                if: ${{ matrix.php != '7.4' }}
+                run: './vendor/bin/phpunit -v tests'
+            -
+                run: 'python3 demo/client/python/test.py'
+            -
+                run: 'perl demo/client/perl/test.pl'
+            -
+                if: ${{ failure() }}
+                run: |
+                    #env
+                    #php -i
+                    #ps auxwww
+                    #dpkg --list | grep php
+                    #ps auxwww | grep fpm
+                    #pwd
+                    #sudo env
+                    #systemctl status apache2.service
+                    #ls -la /etc/apache2/mods-enabled
+                    #ls -la /etc/apache2/conf-enabled
+                    #ls -la /etc/apache2/mods-available
+                    #ls -la /etc/apache2/conf-available
+                    #ls -la /etc/apache2/sites-available/
+                    #sudo cat /etc/apache2/envvars
+                    #sudo cat /etc/apache2/sites-available/000-default.conf
+                    #ls -ltr /var/log
+                    #ls -ltr /var/log/apache2
+                    sudo cat /var/log/privoxy/*
+                    sudo cat /var/log/apache2/error.log
+                    sudo cat /var/log/apache2/other_vhosts_access.log
+                    sudo cat /var/log/php*.log
index 1305331..f589b5d 100644 (file)
@@ -1,6 +1,5 @@
 /.idea
-composer.phar
-composer.lock
-/vendor/*
-/tests/coverage/*
 /build/*
+/vendor/*
+/.phpunit.result.cache
+/composer.lock
diff --git a/php/phpxmlrpc/.travis.yml b/php/phpxmlrpc/.travis.yml
deleted file mode 100644 (file)
index 38878b6..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-language: php
-
-php:
-  - 5.3
-  - 5.4
-  - 5.5
-  - 5.6
-  - 7.0
-  - 7.1
-  # hhvm is not available any more on default travis images
-  #- hhvm
-
-before_install:
-  # This is mandatory or the 'apt-get install' calls following will fail
-  - sudo apt-get update -qq
-  - sudo apt-get install -y apache2 libapache2-mod-fastcgi
-  - sudo apt-get install -y privoxy
-
-install:
-  - composer self-update && composer install
-
-before_script:
-  # Disable xdebug for speed.
-  # NB: this should NOT be done for hhvm and php 7.0.
-  # Also we use the php 5.6 run to generate code coverage reports, and we need xdebug for that
-  - if [ "$TRAVIS_PHP_VERSION" != "hhvm" -a "$TRAVIS_PHP_VERSION" != "7.0" -a "$TRAVIS_PHP_VERSION" != "5.6" ]; then phpenv config-rm xdebug.ini; fi
-
-  # Set up Apache and Privoxy instances inside the Travis VM and use them for testing against
-  - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then ./tests/ci/travis/setup_php_fpm.sh; ./tests/ci/travis/setup_apache.sh; fi
-  - if [ "$TRAVIS_PHP_VERSION" = "hhvm" ]; then ./tests/ci/travis/setup_hhvm.sh; ./tests/ci/travis/setup_apache_hhvm.sh; fi
-  - ./tests/ci/travis/setup_privoxy.sh
-
-  # output what version of phpunit we got going
-  - vendor/bin/phpunit --version
-
-script:
-  # Travis currently compiles PHP with an oldish cURL/GnuTLS combination;
-  # to make the tests pass when Apache has a bogus SSL cert whe need the full set of options below
-  vendor/bin/phpunit --coverage-clover=coverage.clover tests LOCALSERVER=localhost URI=/demo/server/server.php HTTPSSERVER=localhost HTTPSURI=/demo/server/server.php PROXY=localhost:8080 HTTPSVERIFYHOST=0 HTTPSIGNOREPEER=1 SSLVERSION=3 DEBUG=1
-
-after_failure:
-  # Save as much info as we can to help developers
-  - cat apache_error.log
-  - cat apache_access.log
-  #- cat /var/log/hhvm/error.log
-  #- if [ "$TRAVIS_PHP_VERSION" = "hhvm" ]; then php -i; fi
-
-after_script:
-  # Upload code-coverage to Scrutinizer
-  - if [ "$TRAVIS_PHP_VERSION" = "5.6" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi
-  - if [ "$TRAVIS_PHP_VERSION" = "5.6" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
-  # Upload code-coverage CodeClimate
-  - if [ "$TRAVIS_PHP_VERSION" = "5.6" ]; then CODECLIMATE_REPO_TOKEN=7fa6ee01e345090e059e5e42f3bfbcc8692feb8340396382dd76390a3019ac13 ./vendor/bin/test-reporter --coverage-report=coverage.clover; fi
index f78f8f0..6ab8515 100644 (file)
@@ -8,11 +8,10 @@ The following requirements should be met prior to using 'XMLRPC for PHP':
 
 * PHP 5.3.0 or later
 
-* the php "curl" extension is needed if you wish to use SSL or HTTP 1.1 to
-  communicate with remote servers
+* the php "curl" extension is needed if you wish to use SSL or HTTP 1.1 to communicate with remote servers
 
-The php "xmlrpc" native extension is not required, but if it is installed,
-there will be no interference with the operation of this library.
+The php "xmlrpc" native extension is not required, but if it is installed, there will be no interference with the
+operation of this library.
 
 
 Installation instructions
@@ -30,7 +29,7 @@ Installation of the library is quite easy:
 
     3.  Open a terminal and use Composer to grab the library.
 
-            $ composer require phpxmlrpc/phpxmlrpc:4.0
+            $ composer require phpxmlrpc/phpxmlrpc:^4
 
     4.  Write your code.
         Once Composer has downloaded the component(s), all you need to do is include the vendor/autoload.php file that
index b206cc1..99046b1 100644 (file)
@@ -1,6 +1,171 @@
+XML-RPC for PHP version 4.6.1 - 2022/2/15
+
+* fixed: one php warning with php 8 and up
+
+
+XML-RPC for PHP version 4.6.0 - 2021/12/9
+
+* fixed: compatibility with php 8.1
+
+* improved: when encoding utf8 text into us-ascii xml, use character entity references for characters number 0-31
+  (ascii non printable characters), as we were already doing when encoding iso-8859-1 text into us-ascii xml
+
+* new: method `Server::getDispatchMap()`. Useful for non-child classes which want to f.e. introspect the server
+
+* new: increase flexibility in class composition by adopting a Dependency Injection (...ish) pattern:
+  it is now possible to swap out the Logger, XMLParser and Charset classes with similar ones of your own making.
+  Example code:
+      // 1. create an instance of a custom character encoder
+      // $myCharsetEncoder = ...
+      // 2. then use it while serializing a Request:
+      Request::setCharsetEncoder($myCharsetEncoder);
+      $request->serialize($funkyCharset);
+
+* new: method `XMLParser::parse()` acquired a 4th argument
+
+* new: method `Wrapper::wrapPhpClass` allows to customize the names of the phpxmlrpc methods by stripping the original
+  class name and accompanying namespace and replace it with a user-defined prefix, via option `replace_class_name`
+
+* new: `Response` constructor gained a 4th argument
+
+* deprecated: properties `Response::hdrs`, `Response::_cookies`, `Response::raw_data`. Use `Response::httpResponse()` instead.
+  That method returns an array which also holds the http response's status code - useful in case of http errors.
+
+* deprecated: method `Request::createPayload`. Use `Request::serialize` instead
+
+* deprecated: property `Request::httpResponse`
+
+* improved: `Http::parseResponseHeaders` now throws a more specific exception in case of http errors
+
+* improved: Continuous Integration is now running on Github Actions instead of Travis
+
+
+XML-RPC for PHP version 4.5.2 - 2021/1/11
+
+* improved: better phpdocs in the the php code generated by the Wrapper class
+
+* improved: debugger favicon and page title when used from the phpjsonrpc library
+
+* fixed: allow `Encoder::decode` to properly support different target character sets for polyfill-xmlrpc decode functions
+
+* improved: allow usage of 'epivals' for the 'parameters_type' member of methods definitions in the Server dispatch map
+
+
+XML-RPC for PHP version 4.5.1 - 2021/1/3
+
+* improved: made it easier to subclass the Helper\Charset class by allowing `instance` to use late static binding
+
+* fixed: reinstated access to xmlrpc_server->dmap (for users of the v3 API)
+
+* fixed: method `xmlrpc_encode_entitites` (for users of the v3 API)
+
+* improved: split the code of the demo server in multiple files, describing better the purpose of each
+
+
+XML-RPC for PHP version 4.5.0 - 2020/12/31
+
+* new: it is now possible to control the precision used when serializing DOUBLE values via usage of
+  `PhpXmlRpc::$xmlpc_double_precision`
+
+* fixed: `Encoder::encode` would not correctly encode DateTime and DateTimeImmutable objects
+
+* improvements to to the Helper\Date class in rejecting invalid date strings
+
+* improvements to the Wrapper class in identifying required arguments types from source code phpdoc: support 'array[]',
+  'DateTime' and 'DateTimeImmutable'
+
+* improvements to the support of the XMLRPC extension emulation (now provided by the phpxmlrpc/polyfill-xmlrpc package)
+
+* minor improvements to the Charset helper: it now loads character set conversion  tables on demand, leading to
+  slightly lower memory usage and faster execution time when using UTF8 everywhere.
+  NB: take care if you have subclassed it!
+
+* new method: `Server::isSyscall` - mostly of use to Server subclasses and friend classes such as introspectors
+
+* internal method `XMLParser::xmlrpc_ee` now accepts 3 states for its 3rd parameter instead of a bool
+
+* improvements in the inline phpdoc: tagged many methods and class member as reserved for internal usage only
+
+* minor improvements in the debugger to allow easier integration of phpxmlrpc/jsonrpc and friends
+
+* reorganized the test suite to be more manageable
+
+* removed obsolete files from the 'extras' folder; updated and moved to the 'demo' folders the perl and python
+  client scripts; moved benchmark.php and verify_compat.php to the 'extras' folder
+
+
+XML-RPC for PHP version 4.4.3 - 2020/12/17
+
+* fixed: compatibility with PHP 8.0 (fixes to the debugger, to the server's 'system.methodHelp' method and to the
+  PhpXmlRpc\Wrapper class).
+  Note that method `Value::structeach` has not been removed from the API, but it is _not_ supported when running
+  on PHP 8.0 or later - in that case it will always throw an Error.
+
+* improvements to the test stack: it is now possible to run it via Docker besides Travis; avoid using _any_ external
+  server when running tests; run Travis tests also on php 8.0; bump PHPUnit versions in use
+
+
+XML-RPC for PHP version 4.4.2 - 2020/3/4
+
+* fixed: `client->setCookie()` bug: cookie values that contain spaces are now properly encoded in a way that gets them
+  decoded back to spaces on the receiving end if the server running on php 7.4 (or does RFC-compliant cookie decoding).
+  Beforehand we were encoding spaces to '+' characters.
+
+
+XML-RPC for PHP version 4.4.1 - 2019/7/29
+
+* fixed: allow handling huge xml messages (>=10MB) (issue #71)
+
+* improved: make it easier to overtake the library's usage of `error_log`
+
+
+XML-RPC for PHP version 4.3.2 - 2019/5/27
+
+* fixed: remove one php 7.2 warning when using the v3 api
+
+* improved: the Travis tests are now run with all php versions from 5.6 to 7.3. We dropped tests with php 5.3, 5.4 and 5.5
+
+
+XML-RPC for PHP version 4.3.1 - 2018/1/20
+
+* fixed: error when using https in non-curl mode
+
+* fixed: compatibility of tests with php 7.2
+
+* fixed: html injection in sample code
+
+* fixed: warnings emitted by the *legacy* server in xmlrpcs.inc
+
+* fixed: encoding of php variables of type 'resource' when using xmlrpc_encode in php-compatibility mode
+
+* fixed: bad html tag in sample code
+
+* improved: text of error messages
+
+
+XML-RPC for PHP version 4.3.0 - 2017/11/6
+
+* fixed: compatibility with Basic/Digest/NTLM auth when using client in cURL mode (issue #58)
+
+* improved: added unit tests for Basic and Digest http auth. Also improved tests suite
+
+* new: allow to force usage of curl for http 1.0 calls, as well as plain socket for https calls, via the method
+    `Client::setUseCurl()`
+
+
+XML-RPC for PHP version 4.2.2 - 2017/10/15
+
+* fixed: compatibility with Lighttpd target servers when using client in cURL mode and request body size > 1024 bytes (issue #56)
+
+
+XML-RPC for PHP version 4.2.1 - 2017/9/3
+
+* fixed: compatibility with php 7.2 (issue #55)
+
+
 XML-RPC for PHP version 4.2.0 - 2017/6/30
 
-* improved: allow a DateTimeImmutable object also be detected as a date when encoding
+* improved: allow also DateTimeImmutable objects to be detected as a date when encoding
 
 
 XML-RPC for PHP version 4.1.1 - 2016/10/1
@@ -74,7 +239,7 @@ PLEASE READ CAREFULLY THE NOTES BELOW to insure a smooth upgrade.
 * improved: debug messages are not html-escaped any more when executing from the command line
 
 * improved: the library is now tested using Travis ( https://travis-ci.org/ ).
-  Tests are executed using all php versions from 5.3 to 7.0 nightly, plus HHVM; code-coverage information
+  Tests are executed using all php versions from 5.3 to 7.2; code-coverage information
   is generated using php 5.6 and uploaded to both Code Coverage and Scrutinizer online services
 
 * improved: phpunit is now installed via composer, not bundled anymore
@@ -606,7 +771,7 @@ New features include:
    xmlrpc_client::setCredentials method.
 
  * Added test script and method for verifying correct passing of
-        booleans
+   booleans
 
 The changelog is available at: http://xmlrpc.usefulinc.com/ChangeLog.txt
 
index 2b48d5e..64d829c 100644 (file)
@@ -11,13 +11,16 @@ Detailed installation instructions are in the [INSTALL.md](INSTALL.md) file, alo
 Documentation
 -------------
 
-*NB: the user manual has not been updated yet with all the changes made in version 4. Please consider it unreliable!*
-
-*You are encouraged to look instead the code examples found in the demo/ directory*
+See the documentation page at [gggeek.github.io/phpxmlrpc](https://gggeek.github.io/phpxmlrpc) for a list of the
+library main features and all project related information.
 
 The user manual can be found in the doc/manual directory, in Asciidoc format: [phpxmlrpc_manual.adoc](doc/manual/phpxmlrpc_manual.adoc)
 
-Release tarballs also contain the HTML and PDF versions, as well as an automatically generated API documentation.
+Older release tarballs also contain HTML and PDF versions of the manual, as well as an automatically generated API documentation.
+
+*NB: the user manual has not been updated yet with all the changes made in version 4. Please consider it unreliable!*
+
+*You are encouraged to look instead the code examples found in the demo/ directory*
 
 Upgrading
 ---------
@@ -30,19 +33,36 @@ If you are upgrading from version 3 or earlier you have two options:
 In any case, read carefully the docs in [doc/api_changes_v4.md](doc/api_changes_v4.md) and report back any undocumented
 issue using GitHub.
 
+Running tests
+-------------
+
+The recommended way to run the library test suite is via the provided Docker containers.
+A handy shell script is available that simplifies usage of Docker.
+
+The full sequence of operations is:
+
+    ./tests/ci/vm.sh build
+    ./tests/ci/vm.sh start
+    ./tests/ci/vm.sh runtests
+    ./tests/ci/vm.sh stop
+
+    # and, once you have finished all testing related work:
+    ./tests/ci/vm.sh cleanup
+
+By default tests are run using php 7.2 in a Container based on Ubuntu 18 Bionic.
+You can change the version of PHP and Ubuntu in use by setting the environment variables PHP_VERSION and UBUNTU_VERSION
+before building the Container.
+
+To generate the code-coverage report, run `./tests/ci/vm.sh runcoverage`
+
 License
 -------
 Use of this software is subject to the terms in the [license.txt](license.txt) file
 
-SSL-certificate
----------------
-The passphrase for the rsakey.pem certificate is 'test'.
-
 
 [![License](https://poser.pugx.org/phpxmlrpc/phpxmlrpc/license)](https://packagist.org/packages/phpxmlrpc/phpxmlrpc)
 [![Latest Stable Version](https://poser.pugx.org/phpxmlrpc/phpxmlrpc/v/stable)](https://packagist.org/packages/phpxmlrpc/phpxmlrpc)
 [![Total Downloads](https://poser.pugx.org/phpxmlrpc/phpxmlrpc/downloads)](https://packagist.org/packages/phpxmlrpc/phpxmlrpc)
 
-[![Build Status](https://travis-ci.org/gggeek/phpxmlrpc.svg?branch=php53)](https://travis-ci.org/gggeek/phpxmlrpc)
-[![Test Coverage](https://codeclimate.com/github/gggeek/phpxmlrpc/badges/coverage.svg)](https://codeclimate.com/github/gggeek/phpxmlrpc)
-[![Code Coverage](https://scrutinizer-ci.com/g/gggeek/phpxmlrpc/badges/coverage.png?b=php53)](https://scrutinizer-ci.com/g/gggeek/phpxmlrpc/?branch=php53)
+[![Build Status](https://github.com/gggeek/phpxmlrpc/actions/workflows/ci.yml/badge.svg)](https://github.com/gggeek/phpxmlrpc/actions/workflows/ci.yml)
+[![Code Coverage](https://scrutinizer-ci.com/g/gggeek/phpxmlrpc/badges/coverage.png)](https://scrutinizer-ci.com/g/gggeek/phpxmlrpc)
index b3cfafa..3a241a5 100644 (file)
@@ -2,44 +2,33 @@
     "name": "phpxmlrpc/phpxmlrpc",
     "description": "A php library for building xmlrpc clients and servers",
     "license": "BSD-3-Clause",
-    "homepage": "http://gggeek.github.io/phpxmlrpc/",
-    "keywords": [ "xmlrpc", "webservices" ],
+    "homepage": "https://gggeek.github.io/phpxmlrpc/",
+    "keywords": [ "xmlrpc", "xml-rpc","webservices" ],
     "require": {
-        "php": ">=5.3.0",
+        "php": "^5.3.0 || ^7.0 || ^8.0",
         "ext-xml": "*"
     },
+    "_comment::tests": "The dev packages below require a minimum of php 5.6, even though we support php 5.3. Can we manage to do better?",
     "require-dev": {
-        "phpunit/phpunit": ">=4.0.0, <6.0.0",
+        "phpunit/phpunit": "^5.0 || ^8.5.14",
         "phpunit/phpunit-selenium": "*",
-        "codeclimate/php-test-reporter": "dev-master",
+        "yoast/phpunit-polyfills": "*",
         "ext-curl": "*",
-        "ext-mbstring": "*",
-        "indeyets/pake": "~1.99",
-        "sami/sami": "~3.1",
-        "docbook/docbook-xsl": "~1.78"
+        "ext-dom": "*",
+        "ext-mbstring": "*"
     },
     "suggest": {
         "ext-curl": "Needed for HTTPS and HTTP 1.1 support, NTLM Auth etc...",
         "ext-zlib": "Needed for sending compressed requests and receiving compressed responses, if cURL is not available",
-        "ext-mbstring": "Needed to allow reception of requests/responses in character sets other than ASCII,LATIN-1,UTF-8"
+        "ext-mbstring": "Needed to allow reception of requests/responses in character sets other than ASCII,LATIN-1,UTF-8",
+        "phpxmlrpc/extras": "Adds more featured Server classes and other useful bits",
+        "phpxmlrpc/jsonrpc": "Adds support for the JSON-RPC protocol"
+    },
+    "_comment::conflict": "Within the extras package, only the XMLRPC extension emulation is not compatible... the JSONRPC part should be ok. Both have been moved to different packages anyway",
+    "conflict": {
+        "phpxmlrpc/extras": "<= 0.6.3"
     },
     "autoload": {
         "psr-4": {"PhpXmlRpc\\": "src/"}
-    },
-    "config": {
-        "secure-http": false
-    },
-    "repositories": [
-        {
-            "type": "package",
-            "package": {
-                "name": "docbook/docbook-xsl",
-                "version": "1.78.1",
-                "dist": {
-                    "url": "https://sourceforge.net/projects/docbook/files/docbook-xsl/1.78.1/docbook-xsl-1.78.1.zip/download",
-                    "type": "zip"
-                }
-            }
-        }
-    ]
+    }
 }
index 0d0a649..09f9b88 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author Gaetano Giunta
- * @copyright (C) 2005-2015 G. Giunta
+ * @copyright (C) 2005-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  *
  * @todo switch params for http compression from 0,1,2 to values to be used directly
@@ -14,9 +14,10 @@ header('Content-Type: text/html; charset=utf-8');
 ?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
 <head>
-    <title>XMLRPC Debugger</title>
+    <link rel="icon" type="image/vnd.microsoft.icon" href="favicon.ico">
+    <title><?php if (defined('DEFAULT_WSTYPE') && DEFAULT_WSTYPE == 1) echo 'JSONRPC'; else echo 'XMLRPC'; ?> Debugger</title>
     <meta name="robots" content="index,nofollow"/>
     <style type="text/css">
         <!--
@@ -92,10 +93,8 @@ header('Content-Type: text/html; charset=utf-8');
 <?php
 
 include __DIR__ . '/common.php';
-if ($action) {
 
-    include_once __DIR__ . "/../src/Autoloader.php";
-    PhpXmlRpc\Autoloader::register();
+if ($action) {
 
     // make sure the script waits long enough for the call to complete...
     if ($timeout) {
@@ -103,16 +102,16 @@ if ($action) {
     }
 
     if ($wstype == 1) {
-        @include 'jsonrpc.inc';
-        if (!class_exists('jsonrpc_client')) {
-            die('Error: to debug the jsonrpc protocol the jsonrpc.inc file is needed');
+        //@include 'jsonrpc.inc';
+        if (!class_exists('\PhpXmlRpc\JsonRpc\Client')) {
+            die('Error: to debug the jsonrpc protocol the phpxmlrpc/jsonrpc package is needed');
         }
-        $clientClass = 'PhpJsRpc\Client';
-        $requestClass = 'PhpJsRpc\Request';
+        $clientClass = '\PhpXmlRpc\JsonRpc\Client';
+        $requestClass = '\PhpXmlRpc\JsonRpc\Request';
         $protoName = 'JSONRPC';
     } else {
-        $clientClass = 'PhpXmlRpc\Client';
-        $requestClass = 'PhpXmlRpc\Request';
+        $clientClass = '\PhpXmlRpc\Client';
+        $requestClass = '\PhpXmlRpc\Request';
         $protoName = 'XMLRPC';
     }
 
@@ -199,7 +198,7 @@ if ($action) {
         case 'wrap':
             $msg[0] = new $requestClass('system.methodHelp', array(), $id);
             $msg[0]->addparam(new PhpXmlRpc\Value($method));
-            $msg[1] = new $requestClass('system.methodSignature', array(), $id + 1);
+            $msg[1] = new $requestClass('system.methodSignature', array(), (int)$id + 1);
             $msg[1]->addparam(new PhpXmlRpc\Value($method));
             $actionname = 'Description of method "' . $method . '"';
             break;
@@ -475,7 +474,7 @@ if ($action) {
                         $encoder = new PhpXmlRpc\Encoder();
                         $msig = $encoder->decode($r2);
                         $msig = $msig[$methodsig];
-                        $proto = $protocol == 2 ? 'https' : $protocol == 1 ? 'http11' : '';
+                        $proto = $protocol == 2 ? 'https' : ( $protocol == 1 ? 'http11' : '' );
                         if ($proxy == '' && $username == '' && !$requestcompression && !$responsecompression &&
                             $clientcookies == ''
                         ) {
@@ -541,6 +540,7 @@ if ($action) {
 
     <h3>Changelog</h3>
     <ul>
+        <li>2020-12-11: fix problems with running the debugger on php 8</li>
         <li>2015-05-30: fix problems with generating method payloads for NIL and Undefined parameters</li>
         <li>2015-04-19: fix problems with LATIN-1 characters in payload</li>
         <li>2007-02-20: add visual editor for method payload; allow strings, bools as jsonrpc msg id</li>
index 4026d60..d6e43fe 100644 (file)
@@ -1,17 +1,55 @@
 <?php
 /**
  * @author Gaetano Giunta
- * @copyright (C) 2005-2015 G. Giunta
+ * @copyright (C) 2005-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  *
  * Parses GET/POST variables
  *
  * @todo switch params for http compression from 0,1,2 to values to be used directly
  * @todo do some more sanitization of received parameters
+ * @todo move parameters away from global namespace
  */
 
+// handle class autoloading:
+if (file_exists(__DIR__.'/../vendor/autoload.php')) {
+    // if the debugger is installed as top-level project with Composer, allow finding classes from dependencies
+    include_once(__DIR__.'/../vendor/autoload.php');
+} else {
+    // assume this is either a standalone install, or installed as Composer dependency
+    /// @todo if the latter is true, should we just not skip using the custom Autoloader, and let a top-level
+    ///       debugger include this one, taking care of autoloading ?
+    include_once __DIR__ . "/../src/Autoloader.php";
+    PhpXmlRpc\Autoloader::register();
+}
+
+// work around register globals - @see https://www.php.net/manual/en/faq.misc.php#faq.misc.registerglobals
+if (ini_get('register_globals')) {
+    function unregister_globals()
+    {
+        // Might want to change this perhaps to a nicer error
+        if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
+            die('GLOBALS overwrite attempt detected');
+        }
+
+        // Variables that shouldn't be unset
+        $noUnset = array('GLOBALS',  '_GET', '_POST', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
+
+        $input = array_merge($_GET, $_POST, $_COOKIE, $_SERVER, $_ENV, $_FILES,
+            isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array()
+        );
+
+        foreach ($input as $k => $v) {
+            if (!in_array($k, $noUnset) && isset($GLOBALS[$k])) {
+                unset($GLOBALS[$k]);
+            }
+        }
+    }
+    unregister_globals();
+}
+
 // work around magic quotes
-if (get_magic_quotes_gpc()) {
+if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
     function stripslashes_deep($value)
     {
         $value = is_array($value) ?
@@ -34,10 +72,11 @@ if (isset($_GET['usepost']) && $_GET['usepost'] === 'true') {
 /// @todo if $inputcharset is not UTF8, we should probably re-encode $_GET to make it UTF-8
 
 // recover input parameters
+/// @todo instead of using globals, move them to an array. Also: use a class for this parsing...
 $debug = false;
 $protocol = 0;
 $run = false;
-$wstype = 0;
+$wstype = defined('DEFAULT_WSTYPE') ? DEFAULT_WSTYPE : 0;
 $id = '';
 if (isset($_GET['action'])) {
     if (isset($_GET['wstype']) && $_GET['wstype'] == '1') {
index 2fd4307..1501ac0 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * @author Gaetano Giunta
- * @copyright (C) 2005-2015 G. Giunta
+ * @copyright (C) 2005-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  *
  * @todo add links to documentation from every option caption
  * @todo add support for more options, such as ntlm auth to proxy, or request charset encoding
  * @todo parse content of payload textarea to be fed to visual editor
  * @todo add http no-cache headers
+ * @todo if jsonrpc php classes are not available, gray out or hide altogether the JSONRPC option & title
+ * @todo if js libs are not available, do not try to load them
  **/
 
-// make sure we set the correct charset type for output, so that we can display all characters
+// Make sure we set the correct charset type for output, so that we can display all characters
 header('Content-Type: text/html; charset=utf-8');
 
 include __DIR__ . '/common.php';
@@ -20,15 +22,17 @@ if ($action == '') {
     $action = 'list';
 }
 
-// relative path to the visual xmlrpc editing dialog
-$editorpath = '../../phpjsrpc/debugger/';
-$editorlibs = '../../phpjsrpc/lib/';
+// Relative path to the visual xmlrpc editing dialog
+// We allow to easily configure this path via defines
+$editorpath = (defined('JSXMLRPC_PATH') ? JSXMLRPC_PATH : '../..') . '/jsxmlrpc/debugger/';
+$editorlibs = (defined('JSXMLRPC_PATH') ? JSXMLRPC_PATH : '../..') . '/jsxmlrpc/lib/';
 ?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
 <head>
-    <title>XMLRPC Debugger</title>
+    <link rel="icon" type="image/vnd.microsoft.icon" href="favicon.ico">
+    <title><?php if (defined('DEFAULT_WSTYPE') && DEFAULT_WSTYPE == 1) echo 'JSONRPC'; else echo 'XMLRPC'; ?> Debugger</title>
     <meta name="robots" content="index,nofollow"/>
     <script type="text/javascript" language="Javascript">
         if (window.name != 'frmcontroller')
@@ -227,9 +231,9 @@ $editorlibs = '../../phpjsrpc/lib/';
         echo ' document.forms[2].submit();';
     } ?>">
 <h1>XMLRPC
-    <form name="frmxmlrpc" style="display: inline;" action="."><input name="yes" type="radio" onclick="switchtransport(0);"/></form>
+    <form name="frmxmlrpc" style="display: inline;" action="."><input name="yes" type="radio" onclick="switchtransport(0);" <?php if (!class_exists('\PhpXmlRpc\Client')) { echo 'disabled="disabled"';} ?>/></form>
     /
-    <form name="frmjsonrpc" style="display: inline;" action="."><input name="yes" type="radio" onclick="switchtransport(1);"/></form>
+    <form name="frmjsonrpc" style="display: inline;" action="."><input name="yes" type="radio" onclick="switchtransport(1);" <?php if (!class_exists('\PhpXmlRpc\JsonRpc\Client')) { echo 'disabled="disabled"';} ?>/></form>
     JSONRPC Debugger (based on the <a href="http://gggeek.github.io/phpxmlrpc/">PHP-XMLRPC</a> library)
 </h1>
 <form name="frmaction" method="get" action="action.php" target="frmaction" onSubmit="switchFormMethod();">
diff --git a/php/phpxmlrpc/debugger/favicon.ico b/php/phpxmlrpc/debugger/favicon.ico
new file mode 100644 (file)
index 0000000..0106e8d
Binary files /dev/null and b/php/phpxmlrpc/debugger/favicon.ico differ
index eff10ea..e7e8bec 100644 (file)
@@ -9,9 +9,10 @@ if (isset($_GET['run'])) {
 ?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
-<html>
+<html lang="en">
 <head>
-    <title>XMLRPC Debugger</title>
+    <link rel="icon" type="image/vnd.microsoft.icon" href="favicon.ico">
+    <title><?php if (defined('DEFAULT_WSTYPE') && DEFAULT_WSTYPE == 1) echo 'JSONRPC'; else echo 'XMLRPC'; ?> Debugger</title>
 </head>
 <frameset rows="360,*">
     <frame name="frmcontroller" src="controller.php<?php echo htmlspecialchars($query); ?>" marginwidth="0"
diff --git a/php/phpxmlrpc/demo/client/_append.php b/php/phpxmlrpc/demo/client/_append.php
new file mode 100644 (file)
index 0000000..da249b3
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+// Out-of-band information: let the client manipulate the server operations.
+// We do this to help the testsuite script: do not reproduce in production!
+if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
+    include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/append.php";
+}
diff --git a/php/phpxmlrpc/demo/client/_prepend.php b/php/phpxmlrpc/demo/client/_prepend.php
new file mode 100644 (file)
index 0000000..710c477
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Hackish code used to make the demos both viewable as source, runnable, and viewable as html
+ */
+
+if (isset($_GET['showSource']) && $_GET['showSource']) {
+    $file = debug_backtrace()[0]['file'];
+    highlight_file($file);
+    die();
+}
+
+// Make errors visible
+ini_set('display_errors', true);
+error_reporting(E_ALL);
+
+// Use the custom class autoloader. These two lines not needed when the phpxmlrpc library is installed using Composer
+include_once __DIR__ . '/../../src/Autoloader.php';
+PhpXmlRpc\Autoloader::register();
+
+// Let unit tests run against localhost, 'plain' demos against a known public server
+if (isset($_SERVER['HTTPSERVER'])) {
+    define('XMLRPCSERVER', 'http://'.$_SERVER['HTTPSERVER'].'/demo/server/server.php');
+} else {
+    define('XMLRPCSERVER', 'http://gggeek.altervista.org/sw/xmlrpc/demo/server/server.php');
+}
+
+// Out-of-band information: let the client manipulate the page operations.
+// We do this to help the testsuite script: do not reproduce in production!
+if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
+    $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = '/tmp/phpxmlrpc_coverage';
+    if (!is_dir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'])) {
+        mkdir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']);
+        chmod($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'], 0777);
+    }
+
+    include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/prepend.php";
+}
index 90622d2..9ad397f 100644 (file)
@@ -1,4 +1,4 @@
-<html>
+<?php require_once __DIR__ . "/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc - Agesort demo</title></head>
 <body>
 <h1>Agesort demo</h1>
@@ -7,20 +7,17 @@
 
 <h3>The source code demonstrates basic lib usage, including handling of xmlrpc arrays and structs</h3>
 
-<p></p>
+<p>You can see the source to this page here: <a href="agesort.php?showSource=1">agesort.php</a></p>
 <?php
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
-
 $inAr = array("Dave" => 24, "Edd" => 45, "Joe" => 37, "Fred" => 27);
 print "This is the input data:<br/><pre>";
-foreach($inAr as $key => $val) {
+foreach ($inAr as $key => $val) {
     print $key . ", " . $val . "\n";
 }
 print "</pre>";
 
-// create parameters from the input array: an xmlrpc array of xmlrpc structs
+// Create parameters from the input array: an xmlrpc array of xmlrpc structs
 $p = array();
 foreach ($inAr as $key => $val) {
     $p[] = new PhpXmlRpc\Value(
@@ -36,7 +33,7 @@ print "Encoded into xmlrpc format it looks like this: <pre>\n" . htmlentities($v
 
 // create client and message objects
 $req = new PhpXmlRpc\Request('examples.sortByAge', array($v));
-$client = new PhpXmlRpc\Client("http://phpxmlrpc.sourceforge.net/server.php");
+$client = new PhpXmlRpc\Client(XMLRPCSERVER);
 
 // set maximum debug level, to have the complete communication printed to screen
 $client->setDebug(2);
@@ -65,4 +62,4 @@ if (!$resp->faultCode()) {
 
 ?>
 </body>
-</html>
+</html><?php require_once __DIR__ . "/_append.php"; ?>
index 69ce3e0..a83d907 100644 (file)
@@ -1,4 +1,4 @@
-<html>
+<?php require_once __DIR__ . "/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc - Getstatename demo</title></head>
 <body>
 <h1>Getstatename demo</h1>
@@ -6,10 +6,9 @@
 <h2>Send a U.S. state number to the server and get back the state name</h2>
 
 <h3>The code demonstrates usage of automatic encoding/decoding of php variables into xmlrpc values</h3>
-<?php
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
+<p>You can see the source to this page here: <a href="getstatename.php?showSource=1">getstatename.php</a></p>
+<?php
 
 if (isset($_POST["stateno"]) && $_POST["stateno"] != "") {
     $stateNo = (integer)$_POST["stateno"];
@@ -18,13 +17,13 @@ if (isset($_POST["stateno"]) && $_POST["stateno"] != "") {
         array($encoder->encode($stateNo))
     );
     print "Sending the following request:<pre>\n\n" . htmlentities($req->serialize()) . "\n\n</pre>Debug info of server data follows...\n\n";
-    $client = new PhpXmlRpc\Client("http://phpxmlrpc.sourceforge.net/server.php");
+    $client = new PhpXmlRpc\Client(XMLRPCSERVER);
     $client->setDebug(1);
     $r = $client->send($req);
     if (!$r->faultCode()) {
         $v = $r->value();
         print "<br/>State number <b>" . $stateNo . "</b> is <b>"
-            . htmlspecialchars($encoder->decode($v)) . "</b><br/>";
+            . htmlspecialchars($encoder->decode($v)) . "</b><br/><br/>";
     } else {
         print "An error occurred: ";
         print "Code: " . htmlspecialchars($r->faultCode())
@@ -40,4 +39,4 @@ print "<form action=\"getstatename.php\" method=\"POST\">
 
 ?>
 </body>
-</html>
+</html><?php require_once __DIR__ . "/_append.php"; ?>
index 7870a94..f8bb6b0 100644 (file)
@@ -1,14 +1,12 @@
-<html>
+<?php require_once __DIR__ . "/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc - Introspect demo</title></head>
 <body>
 <h1>Introspect demo</h1>
 <h2>Query server for available methods and their description</h2>
 <h3>The code demonstrates usage of multicall and introspection methods</h3>
+<p>You can see the source to this page here: <a href="introspect.php?showSource=1">introspect.php</a></p>
 <?php
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
-
 function display_error($r)
 {
     print "An error occurred: ";
@@ -16,7 +14,7 @@ function display_error($r)
         . " Reason: '" . $r->faultString() . "'<br/>";
 }
 
-$client = new PhpXmlRpc\Client("http://phpxmlrpc.sourceforge.net/server.php");
+$client = new PhpXmlRpc\Client(XMLRPCSERVER);
 
 // First off, let's retrieve the list of methods available on the remote server
 print "<h3>methods available at http://" . $client->server . $client->path . "</h3>\n";
@@ -61,12 +59,12 @@ if ($resp->faultCode()) {
             if ($val->kindOf() == "array") {
                 foreach ($val as $x) {
                     $ret = $x[0];
-                    print "<code>" . $ret->scalarval() . " "
-                        . $methodName->scalarval() . "(";
+                    print "<code>" . htmlspecialchars($ret->scalarval()) . " "
+                        . htmlspecialchars($methodName->scalarval()) . "(";
                     if ($x->count() > 1) {
                         for ($k = 1; $k < $x->count(); $k++) {
                             $y = $x[$k];
-                            print $y->scalarval();
+                            print htmlspecialchars($y->scalarval());
                             if ($k < $x->count() - 1) {
                                 print ", ";
                             }
@@ -83,4 +81,4 @@ if ($resp->faultCode()) {
 }
 ?>
 </body>
-</html>
+</html><?php require_once __DIR__ . "/_append.php"; ?>
index 9486e09..4907a7a 100644 (file)
@@ -1,11 +1,4 @@
-<?php
-// Allow users to see the source of this file even if PHP is not configured for it
-if (isset($_GET['showSource']) && $_GET['showSource']) {
-    highlight_file(__FILE__);
-    die();
-}
-?>
-<html>
+<?php require_once __DIR__ . "/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc - Mail demo</title></head>
 <body>
 <h1>Mail demo</h1>
@@ -14,17 +7,14 @@ if (isset($_GET['showSource']) && $_GET['showSource']) {
     When you press <kbd>Send</kbd> this page will reload, showing you the XML-RPC request sent to the host server, the
     XML-RPC response received and the internal evaluation done by the PHP implementation.</p>
 
-<p>You can find the source to this page here: <a href="mail.php?showSource=1">mail.php</a><br/>
+<p>You can see the source to this page here: <a href="mail.php?showSource=1">mail.php</a><br/>
     And the source to a functionally identical mail-by-XML-RPC server in the file <a
         href="../server/server.php?showSource=1">server.php</a> included with the library (look for the 'mail_send'
     method)</p>
 <?php
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
-
 if (isset($_POST["mailto"]) && $_POST["mailto"]) {
-    $server = "http://phpxmlrpc.sourceforge.net/server.php";
+    $server = XMLRPCSERVER;
     $req = new PhpXmlRpc\Request('mail.send', array(
         new PhpXmlRpc\Value($_POST["mailto"]),
         new PhpXmlRpc\Value($_POST["mailsub"]),
@@ -41,7 +31,7 @@ if (isset($_POST["mailto"]) && $_POST["mailto"]) {
     if (!$resp->faultCode()) {
         print "Mail sent OK<br/>\n";
     } else {
-        print "<fonr color=\"red\">";
+        print "<font color=\"red\">";
         print "Mail send failed<br/>\n";
         print "Fault: ";
         print "Code: " . htmlspecialchars($resp->faultCode()) .
@@ -63,4 +53,4 @@ if (isset($_POST["mailto"]) && $_POST["mailto"]) {
     <input type="Submit" value="Send"/>
 </form>
 </body>
-</html>
+</html><?php require_once __DIR__ . "/_append.php"; ?>
diff --git a/php/phpxmlrpc/demo/client/perl/test.pl b/php/phpxmlrpc/demo/client/perl/test.pl
new file mode 100644 (file)
index 0000000..8eee374
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/local/bin/perl
+
+use Frontier::Client;
+
+my $serverURL='http://localhost/demo/server/server.php';
+
+# try the simplest example
+
+my $client = Frontier::Client->new(
+    'url' => $serverURL, 'debug' => 0, 'encoding' => 'iso-8859-1'
+);
+my $resp = $client->call("examples.getStateName", 32);
+
+print "Got '${resp}'\n";
+
+# now send a mail to nobody in particular
+
+#$resp = $client->call("mail.send", (
+#    "edd",
+#    "Test",
+#    "Bonjour. Je m'appelle Gérard. Mañana. ",
+#    "freddy",
+#    "",
+#    "",
+#    'text/plain; charset="iso-8859-1"')
+#);
+#
+#if ($resp->value()) {
+#    print "Mail sent OK.\n";
+#} else {
+#    print "Error sending mail.\n";
+#}
+
+# test echoing of characters works fine
+
+$resp = $client->call("examples.echo", 'Three "blind" mice - ' . "See 'how' they run");
+print $resp . "\n";
+
+# test name and age example. this exercises structs and arrays
+
+$resp = $client->call("examples.sortByAge",
+    [
+        { 'name' => 'Dave', 'age' => 35},
+        { 'name' => 'Edd', 'age' => 45 },
+        { 'name' => 'Fred', 'age' => 23 },
+        { 'name' => 'Barney', 'age' => 36 }
+    ]
+);
+
+my $e;
+foreach $e (@$resp) {
+    print $$e{'name'} . ", " . $$e{'age'} . "\n";
+}
+
+# test base64
+
+$resp = $client->call("examples.decode64",
+    $client->base64("TWFyeSBoYWQgYSBsaXR0bGUgbGFtYiBTaGUgdGllZCBpdCB0byBhIHB5bG9u")
+);
+
+print $resp . "\n";
index 7173db0..9b7fce6 100644 (file)
@@ -1,14 +1,12 @@
-<html>
+<?php require_once __DIR__ . "/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc - Proxy demo</title></head>
 <body>
 <h1>proxy demo</h1>
 <h2>Query server using a 'proxy' object</h2>
 <h3>The code demonstrates usage for the terminally lazy. For a more complete proxy, look at at the Wrapper class</h3>
+<p>You can see the source to this page here: <a href="proxy.php?showSource=1">proxy.php</a></p>
 <?php
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
-
 class PhpXmlRpcProxy
 {
     protected $client;
@@ -31,7 +29,7 @@ class PhpXmlRpcProxy
      *
      * @throws Exception
      */
-    function __call($name, $arguments)
+    public function __call($name, $arguments)
     {
         $encoder = new PhpXmlRpc\Encoder();
         $valueArray = array();
@@ -50,11 +48,12 @@ class PhpXmlRpcProxy
             return $resp->value();
         }
     }
-
 }
 
 $stateNo = rand(1, 51);
-$proxy = new PhpXmlRpcProxy(new \PhpXmlRpc\Client('http://phpxmlrpc.sourceforge.net/server.php'));
+$proxy = new PhpXmlRpcProxy(new PhpXmlRpc\Client(XMLRPCSERVER));
 $stateName = $proxy->getStateName($stateNo);
 
-echo "State $stateNo is ".htmlspecialchars($stateName);
\ No newline at end of file
+echo "State $stateNo is ".htmlspecialchars($stateName);
+
+require_once __DIR__ . "/_append.php";
diff --git a/php/phpxmlrpc/demo/client/python/test.py b/php/phpxmlrpc/demo/client/python/test.py
new file mode 100644 (file)
index 0000000..4b599e6
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+# -*- coding: iso-8859-1 -*-
+
+import xmlrpc.client
+import base64
+#import sys
+
+server = xmlrpc.client.ServerProxy("http://localhost/demo/server/server.php")
+
+try:
+    print ("Got '" + server.examples.getStateName(32) + "'")
+
+    # Disabled as demo servers often are prevented from sending mail...
+    #r = server.mail.send(
+    #    "edd", "Test",
+    #    "Bonjour. Je m'appelle Gérard. Mañana. ", "freddy", "", "", 
+    #    'text/plain; charset="iso-8859-1"'
+    #    )
+    #if r:
+    #    print ("Mail sent OK")
+    #else:
+    #    print ("Error sending mail")
+
+    r = server.examples.echo('Three "blind" mice - ' + "See 'how' they run")
+    print (r)
+
+    # name/age example. this exercises structs and arrays
+    a = [ 
+            {'name': 'Dave', 'age': 35}, {'name': 'Edd', 'age': 45 },
+            {'name': 'Fred', 'age': 23}, {'name': 'Barney', 'age': 36 }
+        ]
+    r = server.examples.sortByAge(a)
+    print (r)
+
+    # test base 64
+    r = server.examples.decode64(b'Mary had a little lamb She tied it to a pylon')
+    print (r)
+    
+except xmlrpc.client.Fault as err:
+    print("A fault occurred")
+    print("Fault code: %d" % err.faultCode)
+    print("Fault string: %s" % err.faultString)
index 5d33215..1c1ba36 100644 (file)
@@ -1,20 +1,19 @@
-<html>
+<?php require_once __DIR__ . "/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc - Which toolkit demo</title></head>
 <body>
 <h1>Which toolkit demo</h1>
 <h2>Query server for toolkit information</h2>
 <h3>The code demonstrates usage of the PhpXmlRpc\Encoder class</h3>
+<p>You can see the source to this page here: <a href="which.php?showSource=1">which.php</a></p>
 <?php
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
-
 $req = new PhpXmlRpc\Request('interopEchoTests.whichToolkit', array());
-$client = new PhpXmlRpc\Client("http://phpxmlrpc.sourceforge.net/server.php");
+$client = new PhpXmlRpc\Client(XMLRPCSERVER);
 $resp = $client->send($req);
 if (!$resp->faultCode()) {
     $encoder = new PhpXmlRpc\Encoder();
     $value = $encoder->decode($resp->value());
+    print "Toolkit info:<br/>\n";
     print "<pre>";
     print "name: " . htmlspecialchars($value["toolkitName"]) . "\n";
     print "version: " . htmlspecialchars($value["toolkitVersion"]) . "\n";
@@ -27,4 +26,4 @@ if (!$resp->faultCode()) {
 }
 ?>
 </body>
-</html>
+</html><?php require_once __DIR__ . "/_append.php"; ?>
index c13c55d..4e3db40 100644 (file)
@@ -1,4 +1,4 @@
-<html>
+<?php require_once __DIR__ . "/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc - Webservice wrappper demo</title></head>
 <body>
 <h1>Webservice wrappper demo</h1>
     2) wrapping of remote methods into php functions<br/>
     See also proxy.php for an alternative take
 </h3>
+<p>You can see the source to this page here: <a href="wrap.php?showSource=1">wrap.php</a></p>
 <?php
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
-
-$client = new PhpXmlRpc\Client("http://phpxmlrpc.sourceforge.net/server.php");
+$client = new PhpXmlRpc\Client(XMLRPCSERVER);
 $client->return_type = 'phpvals'; // let client give us back php values instead of xmlrpcvals
 $resp = $client->send(new PhpXmlRpc\Request('system.listMethods'));
 if ($resp->faultCode()) {
@@ -44,10 +42,11 @@ if ($resp->faultCode()) {
     if ($callable) {
         echo "Now testing function for remote method to convert U.S. state number into state name";
         $stateNum = rand(1, 51);
-        // the 2nd parameter gets added to the closure - it is teh debug level to be used for the client
+        // the 2nd parameter gets added to the closure - it is the debug level to be used for the client
         $stateName = $callable($stateNum, 2);
+        echo "State $stateNum is ".htmlspecialchars($stateName);
     }
 }
 ?>
 </body>
-</html>
+</html><?php require_once __DIR__ . "/_append.php"; ?>
diff --git a/php/phpxmlrpc/demo/server/_append.php b/php/phpxmlrpc/demo/server/_append.php
new file mode 100644 (file)
index 0000000..da249b3
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+// Out-of-band information: let the client manipulate the server operations.
+// We do this to help the testsuite script: do not reproduce in production!
+if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
+    include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/append.php";
+}
diff --git a/php/phpxmlrpc/demo/server/_prepend.php b/php/phpxmlrpc/demo/server/_prepend.php
new file mode 100644 (file)
index 0000000..8dbf6cf
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Hackish code used to make the demos both viewable as source, runnable, and viewable as html
+ */
+
+// Make errors visible
+ini_set('display_errors', true);
+error_reporting(E_ALL);
+
+if (isset($_GET['showSource']) && $_GET['showSource']) {
+    $file = debug_backtrace()[0]['file'];
+    highlight_file($file);
+    die();
+}
+
+// Use the custom class autoloader. These two lines not needed when the phpxmlrpc library is installed using Composer
+include_once __DIR__ . '/../../src/Autoloader.php';
+PhpXmlRpc\Autoloader::register();
+
+// Out-of-band information: let the client manipulate the server operations.
+// We do this to help the testsuite script: do not reproduce in production!
+if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
+    $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = '/tmp/phpxmlrpc_coverage';
+    if (!is_dir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'])) {
+        mkdir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']);
+        chmod($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'], 0777);
+    }
+
+    include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/prepend.php";
+}
index ac65209..ca5dd7d 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 
-include_once __DIR__ . "/../../vendor/autoload.php";
+require_once __DIR__ . "/_prepend.php";
 
 use PhpXmlRpc\Value;
 
@@ -85,6 +85,9 @@ function getComments($req)
     }
 }
 
+// NB: take care not to output anything else after this call, as it will mess up the responses and it will be hard to
+// debug. In case you have to do so, at least re-emit a correct Content-Length http header (requires output buffering)
+
 $srv = new PhpXmlRpc\Server(array(
     "discuss.addComment" => array(
         "function" => "addComment",
@@ -97,3 +100,5 @@ $srv = new PhpXmlRpc\Server(array(
         "docstring" => $getComments_doc,
     ),
 ));
+
+require_once __DIR__ . "/_append.php";
diff --git a/php/phpxmlrpc/demo/server/methodProviders/functions.php b/php/phpxmlrpc/demo/server/methodProviders/functions.php
new file mode 100644 (file)
index 0000000..898965b
--- /dev/null
@@ -0,0 +1,387 @@
+<?php
+/**
+ * Defines functions and signatures which can be registered as methods exposed by an XMLRPC Server
+ *
+ * To use this, use something akin to:
+ * $signatures = include('functions.php');
+ *
+ * Simplest possible way to implement webservices: create xmlrpc-aware php functions in the global namespace
+ */
+
+use PhpXmlRpc\Encoder;
+use PhpXmlRpc\Response;
+use PhpXmlRpc\Server;
+use PhpXmlRpc\Value;
+
+// a PHP version of the state-number server
+// send me an integer and i'll sell you a state
+
+$GLOBALS['stateNames'] = array(
+    "Alabama", "Alaska", "Arizona", "Arkansas", "California",
+    "Colorado", "Columbia", "Connecticut", "Delaware", "Florida",
+    "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas",
+    "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan",
+    "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada",
+    "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina",
+    "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island",
+    "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont",
+    "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming",
+);
+
+$findstate_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcInt));
+$findstate_doc = 'When passed an integer between 1 and 51 returns the name of a US state, where the integer is the ' .
+    'index of that state name in an alphabetic order.';
+function findState($req)
+{
+    $err = "";
+    // get the first param
+    $sno = $req->getParam(0);
+
+    // param must be there and of the correct type: server object does the validation for us
+
+    // extract the value of the state number
+    $snv = $sno->scalarval();
+    // look it up in our array (zero-based)
+    if (isset($GLOBALS['stateNames'][$snv - 1])) {
+        $stateName = $GLOBALS['stateNames'][$snv - 1];
+    } else {
+        // not there, so complain
+        $err = "I don't have a state for the index '" . $snv . "'";
+    }
+
+    // if we generated an error, create an error return response
+    if ($err) {
+        return new Response(0, PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser, $err);
+    } else {
+        // otherwise, we create the right response with the state name
+        return new Response(new Value($stateName));
+    }
+}
+
+// Sorting demo
+//
+// send me an array of structs thus:
+//
+// Dave 35
+// Edd  45
+// Fred 23
+// Barney 37
+//
+// and I'll return it to you in sorted order
+
+function agesorter_compare($a, $b)
+{
+    /// @todo move away from usage of globals for such a simple case
+    global $agesorter_arr;
+
+    // don't even ask me _why_ these come padded with hyphens, I couldn't tell you :p
+    $a = str_replace("-", "", $a);
+    $b = str_replace("-", "", $b);
+
+    if ($agesorter_arr[$a] == $agesorter_arr[$b]) {
+        return 0;
+    }
+
+    return ($agesorter_arr[$a] > $agesorter_arr[$b]) ? -1 : 1;
+}
+
+$agesorter_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
+$agesorter_doc = 'Send this method an array of [string, int] structs, eg:
+<pre>
+ Dave   35
+ Edd    45
+ Fred   23
+ Barney 37
+</pre>
+And the array will be returned with the entries sorted by their numbers.
+';
+function ageSorter($req)
+{
+    global $agesorter_arr;
+
+    Server::xmlrpc_debugmsg("Entering 'agesorter'");
+    // get the parameter
+    $sno = $req->getParam(0);
+    // error string for [if|when] things go wrong
+    $err = "";
+    $agar = array();
+
+    $max = $sno->count();
+    Server::xmlrpc_debugmsg("Found $max array elements");
+    foreach ($sno as $i => $rec) {
+        if ($rec->kindOf() != "struct") {
+            $err = "Found non-struct in array at element $i";
+            break;
+        }
+        // extract name and age from struct
+        $n = $rec["name"];
+        $a = $rec["age"];
+        // $n and $a are Values,
+        // so get the scalarval from them
+        $agar[$n->scalarval()] = $a->scalarval();
+    }
+
+    // create the output value
+    $v = new Value(array(), Value::$xmlrpcArray);
+
+    $agesorter_arr = $agar;
+    // hack, must make global as uksort() won't
+    // allow us to pass any other auxiliary information
+    uksort($agesorter_arr, 'agesorter_compare');
+    foreach($agesorter_arr as $key => $val) {
+        // recreate each struct element
+        $v[] = new Value(
+            array(
+                "name" => new Value($key),
+                "age" => new Value($val, "int")
+            ),
+            Value::$xmlrpcStruct
+        );
+    }
+
+    if ($err) {
+        return new Response(0, PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser, $err);
+    } else {
+        return new Response($v);
+    }
+}
+
+$addtwo_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcInt, Value::$xmlrpcInt));
+$addtwo_doc = 'Add two integers together and return the result';
+function addTwo($req)
+{
+    $s = $req->getParam(0);
+    $t = $req->getParam(1);
+
+    return new Response(new Value($s->scalarval() + $t->scalarval(), Value::$xmlrpcInt));
+}
+
+$addtwodouble_sig = array(array(Value::$xmlrpcDouble, Value::$xmlrpcDouble, Value::$xmlrpcDouble));
+$addtwodouble_doc = 'Add two doubles together and return the result';
+function addTwoDouble($req)
+{
+    $s = $req->getParam(0);
+    $t = $req->getParam(1);
+
+    return new Response(new Value($s->scalarval() + $t->scalarval(), Value::$xmlrpcDouble));
+}
+
+$stringecho_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcString));
+$stringecho_doc = 'Accepts a string parameter, returns the string.';
+function stringEcho($req)
+{
+    // just sends back a string
+    return new Response(new Value($req->getParam(0)->scalarval()));
+}
+
+$echoback_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcString));
+$echoback_doc = 'Accepts a string parameter, returns the entire incoming payload';
+function echoBack($req)
+{
+    // just sends back a string with what i got sent to me, just escaped, that's all
+    $s = "I got the following message:\n" . $req->serialize();
+
+    return new Response(new Value($s));
+}
+
+$echosixtyfour_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcBase64));
+$echosixtyfour_doc = 'Accepts a base64 parameter and returns it decoded as a string';
+function echoSixtyFour($req)
+{
+    // Accepts an encoded value, but sends it back as a normal string.
+    // This is to test that base64 encoding is working as expected
+    $incoming = $req->getParam(0);
+
+    return new Response(new Value($incoming->scalarval(), Value::$xmlrpcString));
+}
+
+$bitflipper_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
+$bitflipper_doc = 'Accepts an array of booleans, and returns them inverted';
+function bitFlipper($req)
+{
+    $v = $req->getParam(0);
+    $rv = new Value(array(), Value::$xmlrpcArray);
+
+    foreach ($v as $b) {
+        if ($b->scalarval()) {
+            $rv[] = new Value(false, Value::$xmlrpcBoolean);
+        } else {
+            $rv[] = new Value(true, Value::$xmlrpcBoolean);
+        }
+    }
+
+    return new Response($rv);
+}
+
+$getallheaders_sig = array(array(Value::$xmlrpcStruct));
+$getallheaders_doc = 'Returns a struct containing all the HTTP headers received with the request. Provides limited functionality with IIS';
+function getAllHeaders_xmlrpc($req)
+{
+    $encoder = new Encoder();
+
+    if (function_exists('getallheaders')) {
+        return new Response($encoder->encode(getallheaders()));
+    } else {
+        $headers = array();
+        // IIS: poor man's version of getallheaders
+        foreach ($_SERVER as $key => $val) {
+            if (strpos($key, 'HTTP_') === 0) {
+                $key = ucfirst(str_replace('_', '-', strtolower(substr($key, 5))));
+                $headers[$key] = $val;
+            }
+        }
+
+        return new Response($encoder->encode($headers));
+    }
+}
+
+$setcookies_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcStruct));
+$setcookies_doc = 'Sends to client a response containing a single \'1\' digit, and sets to it http cookies as received in the request (array of structs describing a cookie)';
+function setCookies($req)
+{
+    $encoder = new Encoder();
+    $cookies = $req->getParam(0);
+    foreach ($cookies as $name => $value) {
+        $cookieDesc = $encoder->decode($value);
+        setcookie($name, @$cookieDesc['value'], @$cookieDesc['expires'], @$cookieDesc['path'], @$cookieDesc['domain'], @$cookieDesc['secure']);
+    }
+
+    return new Response(new Value(1, Value::$xmlrpcInt));
+}
+
+$getcookies_sig = array(array(Value::$xmlrpcStruct));
+$getcookies_doc = 'Sends to client a response containing all http cookies as received in the request (as struct)';
+function getCookies($req)
+{
+    $encoder = new Encoder();
+    return new Response($encoder->encode($_COOKIE));
+}
+
+$mailsend_sig = array(array(
+    Value::$xmlrpcBoolean, Value::$xmlrpcString, Value::$xmlrpcString,
+    Value::$xmlrpcString, Value::$xmlrpcString, Value::$xmlrpcString,
+    Value::$xmlrpcString, Value::$xmlrpcString,
+));
+$mailsend_doc = 'mail.send(recipient, subject, text, sender, cc, bcc, mimetype)<br/>
+recipient, cc, and bcc are strings, comma-separated lists of email addresses, as described above.<br/>
+subject is a string, the subject of the message.<br/>
+sender is a string, it\'s the email address of the person sending the message. This string can not be
+a comma-separated list, it must contain a single email address only.<br/>
+text is a string, it contains the body of the message.<br/>
+mimetype, a string, is a standard MIME type, for example, text/plain.
+';
+// WARNING; this functionality depends on the sendmail -t option
+// it may not work with Windows machines properly; particularly
+// the Bcc option. Sneak on your friends at your own risk!
+function mailSend($req)
+{
+    $err = "";
+
+    $mTo = $req->getParam(0);
+    $mSub = $req->getParam(1);
+    $mBody = $req->getParam(2);
+    $mFrom = $req->getParam(3);
+    $mCc = $req->getParam(4);
+    $mBcc = $req->getParam(5);
+    $mMime = $req->getParam(6);
+
+    if ($mTo->scalarval() == "") {
+        $err = "Error, no 'To' field specified";
+    }
+
+    if ($mFrom->scalarval() == "") {
+        $err = "Error, no 'From' field specified";
+    }
+
+    $msgHdr = "From: " . $mFrom->scalarval() . "\n";
+    $msgHdr .= "To: " . $mTo->scalarval() . "\n";
+
+    if ($mCc->scalarval() != "") {
+        $msgHdr .= "Cc: " . $mCc->scalarval() . "\n";
+    }
+    if ($mBcc->scalarval() != "") {
+        $msgHdr .= "Bcc: " . $mBcc->scalarval() . "\n";
+    }
+    if ($mMime->scalarval() != "") {
+        $msgHdr .= "Content-type: " . $mMime->scalarval() . "\n";
+    }
+    $msgHdr .= "X-Mailer: XML-RPC for PHP mailer 1.0";
+
+    if ($err == "") {
+        if (!mail("", $mSub->scalarval(), $mBody->scalarval(), $msgHdr)
+        ) {
+            $err = "Error, could not send the mail.";
+        }
+    }
+
+    if ($err) {
+        return new Response(0, PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser, $err);
+    } else {
+        return new Response(new Value(true, Value::$xmlrpcBoolean));
+    }
+}
+
+return array(
+    "examples.getStateName" => array(
+        "function" => "findState",
+        "signature" => $findstate_sig,
+        "docstring" => $findstate_doc,
+    ),
+    "examples.sortByAge" => array(
+        "function" => "ageSorter",
+        "signature" => $agesorter_sig,
+        "docstring" => $agesorter_doc,
+    ),
+    "examples.addtwo" => array(
+        "function" => "addTwo",
+        "signature" => $addtwo_sig,
+        "docstring" => $addtwo_doc,
+    ),
+    "examples.addtwodouble" => array(
+        "function" => "addTwoDouble",
+        "signature" => $addtwodouble_sig,
+        "docstring" => $addtwodouble_doc,
+    ),
+    "examples.stringecho" => array(
+        "function" => "stringEcho",
+        "signature" => $stringecho_sig,
+        "docstring" => $stringecho_doc,
+    ),
+    "examples.echo" => array(
+        "function" => "echoBack",
+        "signature" => $echoback_sig,
+        "docstring" => $echoback_doc,
+    ),
+    "examples.decode64" => array(
+        "function" => "echoSixtyFour",
+        "signature" => $echosixtyfour_sig,
+        "docstring" => $echosixtyfour_doc,
+    ),
+    "examples.invertBooleans" => array(
+        "function" => "bitFlipper",
+        "signature" => $bitflipper_sig,
+        "docstring" => $bitflipper_doc,
+    ),
+
+    "examples.getallheaders" => array(
+        "function" => 'getAllHeaders_xmlrpc',
+        "signature" => $getallheaders_sig,
+        "docstring" => $getallheaders_doc,
+    ),
+    "examples.setcookies" => array(
+        "function" => 'setCookies',
+        "signature" => $setcookies_sig,
+        "docstring" => $setcookies_doc,
+    ),
+    "examples.getcookies" => array(
+        "function" => 'getCookies',
+        "signature" => $getcookies_sig,
+        "docstring" => $getcookies_doc,
+    ),
+
+    "mail.send" => array(
+        "function" => "mailSend",
+        "signature" => $mailsend_sig,
+        "docstring" => $mailsend_doc,
+    ),
+);
diff --git a/php/phpxmlrpc/demo/server/methodProviders/interop.php b/php/phpxmlrpc/demo/server/methodProviders/interop.php
new file mode 100644 (file)
index 0000000..691a7ec
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Defines functions and signatures which can be registered as methods exposed by an XMLRPC Server.
+ *
+ * To use this, use something akin to:
+ * $signatures = include('interop.php');
+ *
+ * Trivial interop tests
+ * http://www.xmlrpc.com/stories/storyReader$1636
+ */
+
+use PhpXmlRpc\Response;
+use PhpXmlRpc\Value;
+
+$i_echoString_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcString));
+$i_echoString_doc = "Echoes string.";
+
+$i_echoStringArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
+$i_echoStringArray_doc = "Echoes string array.";
+
+$i_echoInteger_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcInt));
+$i_echoInteger_doc = "Echoes integer.";
+
+$i_echoIntegerArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
+$i_echoIntegerArray_doc = "Echoes integer array.";
+
+$i_echoFloat_sig = array(array(Value::$xmlrpcDouble, Value::$xmlrpcDouble));
+$i_echoFloat_doc = "Echoes float.";
+
+$i_echoFloatArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
+$i_echoFloatArray_doc = "Echoes float array.";
+
+$i_echoStruct_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcStruct));
+$i_echoStruct_doc = "Echoes struct.";
+
+$i_echoStructArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
+$i_echoStructArray_doc = "Echoes struct array.";
+
+$i_echoValue_doc = "Echoes any value back.";
+$i_echoValue_sig = array(array(Value::$xmlrpcValue, Value::$xmlrpcValue));
+
+$i_echoBase64_sig = array(array(Value::$xmlrpcBase64, Value::$xmlrpcBase64));
+$i_echoBase64_doc = "Echoes base64.";
+
+$i_echoDate_sig = array(array(Value::$xmlrpcDateTime, Value::$xmlrpcDateTime));
+$i_echoDate_doc = "Echoes dateTime.";
+
+function i_echoParam($req)
+{
+    $s = $req->getParam(0);
+
+    return new Response($s);
+}
+
+function i_echoString($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoInteger($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoFloat($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoStruct($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoStringArray($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoIntegerArray($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoFloatArray($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoStructArray($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoValue($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoBase64($req)
+{
+    return i_echoParam($req);
+}
+
+function i_echoDate($req)
+{
+    return i_echoParam($req);
+}
+
+$i_whichToolkit_sig = array(array(Value::$xmlrpcStruct));
+$i_whichToolkit_doc = "Returns a struct containing the following strings: toolkitDocsUrl, toolkitName, toolkitVersion, toolkitOperatingSystem.";
+
+function i_whichToolkit($req)
+{
+    global $SERVER_SOFTWARE;
+    $ret = array(
+        "toolkitDocsUrl" => "http://phpxmlrpc.sourceforge.net/",
+        "toolkitName" => PhpXmlRpc\PhpXmlRpc::$xmlrpcName,
+        "toolkitVersion" => PhpXmlRpc\PhpXmlRpc::$xmlrpcVersion,
+        "toolkitOperatingSystem" => isset($SERVER_SOFTWARE) ? $SERVER_SOFTWARE : $_SERVER['SERVER_SOFTWARE'],
+    );
+
+    $encoder = new PhpXmlRpc\Encoder();
+    return new Response($encoder->encode($ret));
+}
+
+return array(
+    "interopEchoTests.echoString" => array(
+        "function" => "i_echoString",
+        "signature" => $i_echoString_sig,
+        "docstring" => $i_echoString_doc,
+    ),
+    "interopEchoTests.echoStringArray" => array(
+        "function" => "i_echoStringArray",
+        "signature" => $i_echoStringArray_sig,
+        "docstring" => $i_echoStringArray_doc,
+    ),
+    "interopEchoTests.echoInteger" => array(
+        "function" => "i_echoInteger",
+        "signature" => $i_echoInteger_sig,
+        "docstring" => $i_echoInteger_doc,
+    ),
+    "interopEchoTests.echoIntegerArray" => array(
+        "function" => "i_echoIntegerArray",
+        "signature" => $i_echoIntegerArray_sig,
+        "docstring" => $i_echoIntegerArray_doc,
+    ),
+    "interopEchoTests.echoFloat" => array(
+        "function" => "i_echoFloat",
+        "signature" => $i_echoFloat_sig,
+        "docstring" => $i_echoFloat_doc,
+    ),
+    "interopEchoTests.echoFloatArray" => array(
+        "function" => "i_echoFloatArray",
+        "signature" => $i_echoFloatArray_sig,
+        "docstring" => $i_echoFloatArray_doc,
+    ),
+    "interopEchoTests.echoStruct" => array(
+        "function" => "i_echoStruct",
+        "signature" => $i_echoStruct_sig,
+        "docstring" => $i_echoStruct_doc,
+    ),
+    "interopEchoTests.echoStructArray" => array(
+        "function" => "i_echoStructArray",
+        "signature" => $i_echoStructArray_sig,
+        "docstring" => $i_echoStructArray_doc,
+    ),
+    "interopEchoTests.echoValue" => array(
+        "function" => "i_echoValue",
+        "signature" => $i_echoValue_sig,
+        "docstring" => $i_echoValue_doc,
+    ),
+    "interopEchoTests.echoBase64" => array(
+        "function" => "i_echoBase64",
+        "signature" => $i_echoBase64_sig,
+        "docstring" => $i_echoBase64_doc,
+    ),
+    "interopEchoTests.echoDate" => array(
+        "function" => "i_echoDate",
+        "signature" => $i_echoDate_sig,
+        "docstring" => $i_echoDate_doc,
+    ),
+    "interopEchoTests.whichToolkit" => array(
+        "function" => "i_whichToolkit",
+        "signature" => $i_whichToolkit_sig,
+        "docstring" => $i_whichToolkit_doc,
+    ),
+);
diff --git a/php/phpxmlrpc/demo/server/methodProviders/validator1.php b/php/phpxmlrpc/demo/server/methodProviders/validator1.php
new file mode 100644 (file)
index 0000000..f2f6601
--- /dev/null
@@ -0,0 +1,207 @@
+<?php
+/**
+ * Defines functions and signatures which can be registered as methods exposed by an XMLRPC Server
+ *
+ * To use this, use something akin to:
+ * $signatures = include('validator1.php');
+ *
+ * Validator1 tests
+ */
+
+use PhpXmlRpc\Response;
+use PhpXmlRpc\Value;
+
+$v1_arrayOfStructs_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcArray));
+$v1_arrayOfStructs_doc = 'This handler takes a single parameter, an array of structs, each of which contains at least three elements named moe, larry and curly, all <i4>s. Your handler must add all the struct elements named curly and return the result.';
+function v1_arrayOfStructs($req)
+{
+    $sno = $req->getParam(0);
+    $numCurly = 0;
+    foreach ($sno as $str) {
+        foreach ($str as $key => $val) {
+            if ($key == "curly") {
+                $numCurly += $val->scalarval();
+            }
+        }
+    }
+
+    return new Response(new Value($numCurly, Value::$xmlrpcInt));
+}
+
+$v1_easyStruct_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcStruct));
+$v1_easyStruct_doc = 'This handler takes a single parameter, a struct, containing at least three elements named moe, larry and curly, all &lt;i4&gt;s. Your handler must add the three numbers and return the result.';
+function v1_easyStruct($req)
+{
+    $sno = $req->getParam(0);
+    $moe = $sno["moe"];
+    $larry = $sno["larry"];
+    $curly = $sno["curly"];
+    $num = $moe->scalarval() + $larry->scalarval() + $curly->scalarval();
+
+    return new Response(new Value($num, Value::$xmlrpcInt));
+}
+
+$v1_echoStruct_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcStruct));
+$v1_echoStruct_doc = 'This handler takes a single parameter, a struct. Your handler must return the struct.';
+function v1_echoStruct($req)
+{
+    $sno = $req->getParam(0);
+
+    return new Response($sno);
+}
+
+$v1_manyTypes_sig = array(array(
+    Value::$xmlrpcArray, Value::$xmlrpcInt, Value::$xmlrpcBoolean,
+    Value::$xmlrpcString, Value::$xmlrpcDouble, Value::$xmlrpcDateTime,
+    Value::$xmlrpcBase64,
+));
+$v1_manyTypes_doc = 'This handler takes six parameters, and returns an array containing all the parameters.';
+function v1_manyTypes($req)
+{
+    return new Response(new Value(
+        array(
+            $req->getParam(0),
+            $req->getParam(1),
+            $req->getParam(2),
+            $req->getParam(3),
+            $req->getParam(4),
+            $req->getParam(5)
+        ),
+        Value::$xmlrpcArray
+    ));
+}
+
+$v1_moderateSizeArrayCheck_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcArray));
+$v1_moderateSizeArrayCheck_doc = 'This handler takes a single parameter, which is an array containing between 100 and 200 elements. Each of the items is a string, your handler must return a string containing the concatenated text of the first and last elements.';
+function v1_moderateSizeArrayCheck($req)
+{
+    $ar = $req->getParam(0);
+    $sz = $ar->count();
+    $first = $ar[0];
+    $last = $ar[$sz - 1];
+
+    return new Response(new Value($first->scalarval() .
+        $last->scalarval(), Value::$xmlrpcString));
+}
+
+$v1_simpleStructReturn_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcInt));
+$v1_simpleStructReturn_doc = 'This handler takes one parameter, and returns a struct containing three elements, times10, times100 and times1000, the result of multiplying the number by 10, 100 and 1000.';
+function v1_simpleStructReturn($req)
+{
+    $sno = $req->getParam(0);
+    $v = $sno->scalarval();
+
+    return new Response(new Value(
+        array(
+            "times10" => new Value($v * 10, Value::$xmlrpcInt),
+            "times100" => new Value($v * 100, Value::$xmlrpcInt),
+            "times1000" => new Value($v * 1000, Value::$xmlrpcInt)
+        ),
+        Value::$xmlrpcStruct
+    ));
+}
+
+$v1_nestedStruct_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcStruct));
+$v1_nestedStruct_doc = 'This handler takes a single parameter, a struct, that models a daily calendar. At the top level, there is one struct for each year. Each year is broken down into months, and months into days. Most of the days are empty in the struct you receive, but the entry for April 1, 2000 contains a least three elements named moe, larry and curly, all &lt;i4&gt;s. Your handler must add the three numbers and return the result.';
+function v1_nestedStruct($req)
+{
+    $sno = $req->getParam(0);
+
+    $twoK = $sno["2000"];
+    $april = $twoK["04"];
+    $fools = $april["01"];
+    $curly = $fools["curly"];
+    $larry = $fools["larry"];
+    $moe = $fools["moe"];
+
+    return new Response(new Value($curly->scalarval() + $larry->scalarval() + $moe->scalarval(), Value::$xmlrpcInt));
+}
+
+$v1_countTheEntities_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcString));
+$v1_countTheEntities_doc = 'This handler takes a single parameter, a string, that contains any number of predefined entities, namely &lt;, &gt;, &amp; \' and ".<BR>Your handler must return a struct that contains five fields, all numbers: ctLeftAngleBrackets, ctRightAngleBrackets, ctAmpersands, ctApostrophes, ctQuotes.';
+function v1_countTheEntities($req)
+{
+    $sno = $req->getParam(0);
+    $str = $sno->scalarval();
+    $gt = 0;
+    $lt = 0;
+    $ap = 0;
+    $qu = 0;
+    $amp = 0;
+    for ($i = 0; $i < strlen($str); $i++) {
+        $c = substr($str, $i, 1);
+        switch ($c) {
+            case ">":
+                $gt++;
+                break;
+            case "<":
+                $lt++;
+                break;
+            case "\"":
+                $qu++;
+                break;
+            case "'":
+                $ap++;
+                break;
+            case "&":
+                $amp++;
+                break;
+            default:
+                break;
+        }
+    }
+
+    return new Response(new Value(
+        array(
+            "ctLeftAngleBrackets" => new Value($lt, Value::$xmlrpcInt),
+            "ctRightAngleBrackets" => new Value($gt, Value::$xmlrpcInt),
+            "ctAmpersands" => new Value($amp, Value::$xmlrpcInt),
+            "ctApostrophes" => new Value($ap, Value::$xmlrpcInt),
+            "ctQuotes" => new Value($qu, Value::$xmlrpcInt)
+        ),
+        Value::$xmlrpcStruct
+    ));
+}
+
+return array(
+    "validator1.arrayOfStructsTest" => array(
+        "function" => "v1_arrayOfStructs",
+        "signature" => $v1_arrayOfStructs_sig,
+        "docstring" => $v1_arrayOfStructs_doc,
+    ),
+    "validator1.easyStructTest" => array(
+        "function" => "v1_easyStruct",
+        "signature" => $v1_easyStruct_sig,
+        "docstring" => $v1_easyStruct_doc,
+    ),
+    "validator1.echoStructTest" => array(
+        "function" => "v1_echoStruct",
+        "signature" => $v1_echoStruct_sig,
+        "docstring" => $v1_echoStruct_doc,
+    ),
+    "validator1.manyTypesTest" => array(
+        "function" => "v1_manyTypes",
+        "signature" => $v1_manyTypes_sig,
+        "docstring" => $v1_manyTypes_doc,
+    ),
+    "validator1.moderateSizeArrayCheck" => array(
+        "function" => "v1_moderateSizeArrayCheck",
+        "signature" => $v1_moderateSizeArrayCheck_sig,
+        "docstring" => $v1_moderateSizeArrayCheck_doc,
+    ),
+    "validator1.simpleStructReturnTest" => array(
+        "function" => "v1_simpleStructReturn",
+        "signature" => $v1_simpleStructReturn_sig,
+        "docstring" => $v1_simpleStructReturn_doc,
+    ),
+    "validator1.nestedStructTest" => array(
+        "function" => "v1_nestedStruct",
+        "signature" => $v1_nestedStruct_sig,
+        "docstring" => $v1_nestedStruct_doc,
+    ),
+    "validator1.countTheEntities" => array(
+        "function" => "v1_countTheEntities",
+        "signature" => $v1_countTheEntities_sig,
+        "docstring" => $v1_countTheEntities_doc,
+    ),
+);
diff --git a/php/phpxmlrpc/demo/server/methodProviders/wrapper.php b/php/phpxmlrpc/demo/server/methodProviders/wrapper.php
new file mode 100644 (file)
index 0000000..749670f
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Defines functions and signatures which can be registered as methods exposed by an XMLRPC Server
+ *
+ * To use this, use something akin to:
+ * $signatures = include('wrapper.php');
+ *
+ * Wrap methods of xmlrpc-unaware php classes and xmlrpc-unaware php functions so that they can be used transparently.
+ */
+
+use PhpXmlRpc\Response;
+use PhpXmlRpc\Value;
+
+// *** functions ***
+
+/**
+ * Inner code of the state-number server.
+ * Used to test wrapping of PHP functions into xmlrpc methods.
+ *
+ * @param integer $stateNo the state number
+ *
+ * @return string the name of the state (or error description)
+ *
+ * @throws Exception if state is not found
+ */
+function plain_findstate($stateNo)
+{
+    global $stateNames;
+
+    if (isset($stateNames[$stateNo - 1])) {
+        return $stateNames[$stateNo - 1];
+    } else {
+        // not, there so complain
+        throw new Exception("I don't have a state for the index '" . $stateNo . "'", PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser);
+    }
+}
+
+$wrapper = new PhpXmlRpc\Wrapper();
+
+$findstate2_sig = $wrapper->wrapPhpFunction('plain_findstate');
+
+
+// *** objects/classes ***
+
+/**
+ * Used to test usage of object methods in dispatch maps and in wrapper code.
+ */
+class xmlrpcServerMethodsContainer
+{
+    /**
+     * Method used to test logging of php warnings generated by user functions.
+     * @param PhpXmlRpc\Request $req
+     * @return Response
+     */
+    public function phpWarningGenerator($req)
+    {
+        $a = $undefinedVariable; // this triggers a warning in E_ALL mode, since $undefinedVariable is undefined
+        return new Response(new Value(1, Value::$xmlrpcBoolean));
+    }
+
+    /**
+     * Method used to test catching of exceptions in the server.
+     * @param PhpXmlRpc\Request $req
+     * @throws Exception
+     */
+    public function exceptionGenerator($req)
+    {
+        throw new Exception("it's just a test", 1);
+    }
+
+    /**
+     * @param string $msg
+     */
+    public function debugMessageGenerator($msg)
+    {
+        PhpXmlRpc\Server::xmlrpc_debugmsg($msg);
+    }
+
+    /**
+     * A PHP version of the state-number server. Send me an integer and i'll sell you a state.
+     * Used to test wrapping of PHP methods into xmlrpc methods.
+     *
+     * @param integer $num
+     * @return string
+     * @throws Exception
+     */
+    public static function findState($num)
+    {
+        // we are lazy ;-)
+        return plain_findstate($num);
+    }
+
+    /**
+     * Returns an instance of stdClass.
+     * Used to test wrapping of PHP objects with class preservation
+     */
+    public function returnObject()
+    {
+        $obj = new stdClass();
+        $obj->hello = 'world';
+        return $obj;
+    }
+}
+
+$findstate3_sig = $wrapper->wrapPhpFunction(array('xmlrpcServerMethodsContainer', 'findState'));
+
+$obj = new xmlrpcServerMethodsContainer();
+$findstate4_sig = $wrapper->wrapPhpFunction(array($obj, 'findstate'));
+
+$findstate5_sig = $wrapper->wrapPhpFunction('xmlrpcServerMethodsContainer::findState', '', array('return_source' => true));
+eval($findstate5_sig['source']);
+
+$findstate6_sig = $wrapper->wrapPhpFunction('plain_findstate', '', array('return_source' => true));
+eval($findstate6_sig['source']);
+
+$findstate7_sig = $wrapper->wrapPhpFunction(array('xmlrpcServerMethodsContainer', 'findState'), '', array('return_source' => true));
+eval($findstate7_sig['source']);
+
+//$obj = new xmlrpcServerMethodsContainer();
+$findstate8_sig = $wrapper->wrapPhpFunction(array($obj, 'findstate'), '', array('return_source' => true));
+eval($findstate8_sig['source']);
+
+$findstate9_sig = $wrapper->wrapPhpFunction('xmlrpcServerMethodsContainer::findState', '', array('return_source' => true));
+eval($findstate9_sig['source']);
+
+$findstate10_sig = array(
+    /// @todo add a demo/test with a closure
+    "function" => function ($req) { return findState($req); },
+    "signature" => array(array(Value::$xmlrpcString, Value::$xmlrpcInt)),
+    "docstring" => 'When passed an integer between 1 and 51 returns the name of a US state, where the integer is the ' .
+        'index of that state name in an alphabetic order.',
+);
+
+$findstate11_sig = $wrapper->wrapPhpFunction(function ($stateNo) { return plain_findstate($stateNo); });
+
+/// @todo do we really need a new instance ?
+$c = new xmlrpcServerMethodsContainer();
+
+$moreSignatures = $wrapper->wrapPhpClass($c, array('prefix' => 'tests.', 'method_type' => 'all'));
+
+$namespaceSignatures = $wrapper->wrapPhpClass($c, array('prefix' => 'namespacetest.', 'replace_class_name' => true, 'method_filter' => '/^findState$/', 'method_type' => 'static'));
+
+$returnObj_sig =  $wrapper->wrapPhpFunction(array($c, 'returnObject'), '', array('encode_php_objs' => true));
+
+return array_merge(
+    array(
+        'tests.getStateName.2' => $findstate2_sig,
+        'tests.getStateName.3' => $findstate3_sig,
+        'tests.getStateName.4' => $findstate4_sig,
+        'tests.getStateName.5' => $findstate5_sig,
+        'tests.getStateName.6' => $findstate6_sig,
+        'tests.getStateName.7' => $findstate7_sig,
+        'tests.getStateName.8' => $findstate8_sig,
+        'tests.getStateName.9' => $findstate9_sig,
+        'tests.getStateName.10' => $findstate10_sig,
+        'tests.getStateName.11' => $findstate11_sig,
+        'tests.returnPhpObject' => $returnObj_sig,
+    ),
+    $namespaceSignatures,
+    $moreSignatures
+);
index 6e791f4..a0a0f1d 100644 (file)
@@ -3,14 +3,14 @@
  * XMLRPC server acting as proxy for requests to other servers
  * (useful e.g. for ajax-originated calls that can only connect back to
  * the originating server).
+ * For an example of a transparent reverse-proxy, see the ReverseProxy class in package phpxmlrpc/extras
  *
  * @author Gaetano Giunta
- * @copyright (C) 2006-2015 G. Giunta
+ * @copyright (C) 2006-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  */
 
-include_once __DIR__ . "/../../src/Autoloader.php";
-PhpXmlRpc\Autoloader::register();
+require_once __DIR__ . "/_prepend.php";
 
 /**
  * Forward an xmlrpc request to another server, and return to client the response received.
@@ -58,9 +58,9 @@ function forward_request($req)
 
     // build call for remote server
     /// @todo find a way to forward client info (such as IP) to server, either
-    /// - as xml comments in the payload, or
-    /// - using std http header conventions, such as X-forwarded-for...
-    $reqMethod = $encoder->decode($req->getParam(1));
+    ///       - as xml comments in the payload, or
+    ///       - using std http header conventions, such as X-forwarded-for...
+    $reqMethod = $req->getParam(1)->scalarval();
     $pars = $req->getParam(2);
     $req = new PhpXmlRpc\Request($reqMethod);
     foreach ($pars as $par) {
@@ -74,6 +74,8 @@ function forward_request($req)
 }
 
 // run the server
+// NB: take care not to output anything else after this call, as it will mess up the responses and it will be hard to
+// debug. In case you have to do so, at least re-emit a correct Content-Length http header (requires output buffering)
 $server = new PhpXmlRpc\Server(
     array(
         'xmlrpcproxy.call' => array(
@@ -86,3 +88,5 @@ $server = new PhpXmlRpc\Server(
         ),
     )
 );
+
+require_once __DIR__ . "/_append.php";
index b18cf46..4ab3be5 100644 (file)
 /**
  * Demo server for xmlrpc library.
  *
- * Implements a lot of webservices, including a suite of services used for
- * interoperability testing (validator1 methods), and some whose only purpose
- * is to be used for unit-testing the library.
+ * Implements a lot of webservices, including a suite of services used for interoperability testing (validator1 methods),
+ * and some whose only purpose is to be used for unit-testing the library.
  *
- * Please do not copy this file verbatim into your production server.
+ * Please _do not_ copy this file verbatim into your production server.
  **/
 
-// give user a chance to see the source for this server instead of running the services
-if ($_SERVER['REQUEST_METHOD'] != 'POST' && isset($_GET['showSource'])) {
-    highlight_file(__FILE__);
-    die();
-}
-
-include_once __DIR__ . "/../../vendor/autoload.php";
-
-// out-of-band information: let the client manipulate the server operations.
-// we do this to help the testsuite script: do not reproduce in production!
-if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
-    $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = '/tmp/phpxmlrpc_coverage';
-    if (!is_dir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'])) {
-        mkdir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']);
-    }
-
-    include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/prepend.php";
-}
+require_once __DIR__ . "/_prepend.php";
 
+use PhpXmlRpc\PhpXmlRpc;
+use PhpXmlRpc\Response;
+use PhpXmlRpc\Server;
 use PhpXmlRpc\Value;
 
-/**
- * Used to test usage of object methods in dispatch maps and in wrapper code.
- */
-class xmlrpcServerMethodsContainer
-{
-    /**
-     * Method used to test logging of php warnings generated by user functions.
-     * @param PhpXmlRpc\Request $req
-     * @return PhpXmlRpc\Response
-     */
-    public function phpWarningGenerator($req)
-    {
-        $a = $undefinedVariable; // this triggers a warning in E_ALL mode, since $undefinedVariable is undefined
-        return new PhpXmlRpc\Response(new Value(1, Value::$xmlrpcBoolean));
-    }
-
-    /**
-     * Method used to test catching of exceptions in the server.
-     * @param PhpXmlRpc\Request $req
-     * @throws Exception
-     */
-    public function exceptionGenerator($req)
-    {
-        throw new Exception("it's just a test", 1);
-    }
-
-    /**
-     * @param string $msg
-     */
-    public function debugMessageGenerator($msg)
-    {
-        PhpXmlRpc\Server::xmlrpc_debugmsg($msg);
-    }
-
-    /**
-     * A PHP version of the state-number server. Send me an integer and i'll sell you a state.
-     * Used to test wrapping of PHP methods into xmlrpc methods.
-     *
-     * @param integer $num
-     * @return string
-     * @throws Exception
-     */
-    public static function findState($num)
-    {
-        return inner_findstate($num);
-    }
-
-    /**
-     * Returns an instance of stdClass.
-     * Used to test wrapping of PHP objects with class preservation
-     */
-    public function returnObject()
-    {
-        $obj = new stdClass();
-        $obj->hello = 'world';
-        return $obj;
-    }
-}
-
-// a PHP version of the state-number server
-// send me an integer and i'll sell you a state
+// Most of the code used to implement the webservices, and their signatures, are stowed away in neatly organized
+// files, each demoing a different topic
 
-$stateNames = array(
-    "Alabama", "Alaska", "Arizona", "Arkansas", "California",
-    "Colorado", "Columbia", "Connecticut", "Delaware", "Florida",
-    "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas",
-    "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan",
-    "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada",
-    "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina",
-    "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island",
-    "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont",
-    "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming",
-);
+// The simplest way of implementing webservices: as xmlrpc-aware global functions
+$signatures1 = include(__DIR__.'/methodProviders/functions.php');
 
-$findstate_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcInt));
-$findstate_doc = 'When passed an integer between 1 and 51 returns the
-name of a US state, where the integer is the index of that state name
-in an alphabetic order.';
-
-function findState($req)
-{
-    global $stateNames;
+// Examples of exposing as webservices php functions and objects/methods which are not aware of xmlrpc classes
+$signatures2 = include(__DIR__.'/methodProviders/wrapper.php');
 
-    $err = "";
-    // get the first param
-    $sno = $req->getParam(0);
+// Definitions of webservices used for interoperability testing
+$signatures3 = include(__DIR__.'/methodProviders/interop.php');
+$signatures4 = include(__DIR__.'/methodProviders/validator1.php');
 
-    // param must be there and of the correct type: server object does the validation for us
-
-    // extract the value of the state number
-    $snv = $sno->scalarval();
-    // look it up in our array (zero-based)
-    if (isset($stateNames[$snv - 1])) {
-        $stateName = $stateNames[$snv - 1];
-    } else {
-        // not there, so complain
-        $err = "I don't have a state for the index '" . $snv . "'";
-    }
-
-    // if we generated an error, create an error return response
-    if ($err) {
-        return new PhpXmlRpc\Response(0, PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser, $err);
-    } else {
-        // otherwise, we create the right response with the state name
-        return new PhpXmlRpc\Response(new Value($stateName));
-    }
-}
-
-/**
- * Inner code of the state-number server.
- * Used to test wrapping of PHP functions into xmlrpc methods.
- *
- * @param integer $stateNo the state number
- *
- * @return string the name of the state (or error description)
- *
- * @throws Exception if state is not found
- */
-function inner_findstate($stateNo)
-{
-    global $stateNames;
-
-    if (isset($stateNames[$stateNo - 1])) {
-        return $stateNames[$stateNo - 1];
-    } else {
-        // not, there so complain
-        throw new Exception("I don't have a state for the index '" . $stateNo . "'", PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser);
-    }
-}
-
-$wrapper = new PhpXmlRpc\Wrapper();
-
-$findstate2_sig = $wrapper->wrapPhpFunction('inner_findstate');
-
-$findstate3_sig = $wrapper->wrapPhpFunction(array('xmlrpcServerMethodsContainer', 'findState'));
-
-$obj = new xmlrpcServerMethodsContainer();
-$findstate4_sig = $wrapper->wrapPhpFunction(array($obj, 'findstate'));
-
-$findstate5_sig = $wrapper->wrapPhpFunction('xmlrpcServerMethodsContainer::findState', '', array('return_source' => true));
-eval($findstate5_sig['source']);
-
-$findstate6_sig = $wrapper->wrapPhpFunction('inner_findstate', '', array('return_source' => true));
-eval($findstate6_sig['source']);
-
-$findstate7_sig = $wrapper->wrapPhpFunction(array('xmlrpcServerMethodsContainer', 'findState'), '', array('return_source' => true));
-eval($findstate7_sig['source']);
-
-$obj = new xmlrpcServerMethodsContainer();
-$findstate8_sig = $wrapper->wrapPhpFunction(array($obj, 'findstate'), '', array('return_source' => true));
-eval($findstate8_sig['source']);
-
-$findstate9_sig = $wrapper->wrapPhpFunction('xmlrpcServerMethodsContainer::findState', '', array('return_source' => true));
-eval($findstate9_sig['source']);
-
-$findstate10_sig = array(
-    "function" => function ($req) { return findState($req); },
-    "signature" => $findstate_sig,
-    "docstring" => $findstate_doc,
-);
-
-$findstate11_sig = $wrapper->wrapPhpFunction(function ($stateNo) { return inner_findstate($stateNo); });
-
-$c = new xmlrpcServerMethodsContainer;
-$moreSignatures = $wrapper->wrapPhpClass($c, array('prefix' => 'tests.', 'method_type' => 'all'));
-
-$returnObj_sig =  $wrapper->wrapPhpFunction(array($c, 'returnObject'), '', array('encode_php_objs' => true));
+// And finally a few examples inline
 
 // used to test signatures with NULL params
 $findstate12_sig = array(
     array(Value::$xmlrpcString, Value::$xmlrpcInt, Value::$xmlrpcNull),
     array(Value::$xmlrpcString, Value::$xmlrpcNull, Value::$xmlrpcInt),
 );
-
 function findStateWithNulls($req)
 {
     $a = $req->getParam(0);
     $b = $req->getParam(1);
 
     if ($a->scalartyp() == Value::$xmlrpcNull)
-        return new PhpXmlRpc\Response(new Value(inner_findstate($b->scalarval())));
+        return new Response(new Value(plain_findstate($b->scalarval())));
     else
-        return new PhpXmlRpc\Response(new Value(inner_findstate($a->scalarval())));
-}
-
-$addtwo_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcInt, Value::$xmlrpcInt));
-$addtwo_doc = 'Add two integers together and return the result';
-function addTwo($req)
-{
-    $s = $req->getParam(0);
-    $t = $req->getParam(1);
-
-    return new PhpXmlRpc\Response(new Value($s->scalarval() + $t->scalarval(), Value::$xmlrpcInt));
-}
-
-$addtwodouble_sig = array(array(Value::$xmlrpcDouble, Value::$xmlrpcDouble, Value::$xmlrpcDouble));
-$addtwodouble_doc = 'Add two doubles together and return the result';
-function addTwoDouble($req)
-{
-    $s = $req->getParam(0);
-    $t = $req->getParam(1);
-
-    return new PhpXmlRpc\Response(new Value($s->scalarval() + $t->scalarval(), Value::$xmlrpcDouble));
-}
-
-$stringecho_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcString));
-$stringecho_doc = 'Accepts a string parameter, returns the string.';
-function stringEcho($req)
-{
-    // just sends back a string
-    return new PhpXmlRpc\Response(new Value($req->getParam(0)->scalarval()));
-}
-
-$echoback_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcString));
-$echoback_doc = 'Accepts a string parameter, returns the entire incoming payload';
-function echoBack($req)
-{
-    // just sends back a string with what i got sent to me, just escaped, that's all
-    $s = "I got the following message:\n" . $req->serialize();
-
-    return new PhpXmlRpc\Response(new Value($s));
-}
-
-$echosixtyfour_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcBase64));
-$echosixtyfour_doc = 'Accepts a base64 parameter and returns it decoded as a string';
-function echoSixtyFour($req)
-{
-    // Accepts an encoded value, but sends it back as a normal string.
-    // This is to test that base64 encoding is working as expected
-    $incoming = $req->getParam(0);
-
-    return new PhpXmlRpc\Response(new Value($incoming->scalarval(), Value::$xmlrpcString));
-}
-
-$bitflipper_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
-$bitflipper_doc = 'Accepts an array of booleans, and returns them inverted';
-function bitFlipper($req)
-{
-    $v = $req->getParam(0);
-    $rv = new Value(array(), Value::$xmlrpcArray);
-
-    foreach ($v as $b) {
-        if ($b->scalarval()) {
-            $rv[] = new Value(false, Value::$xmlrpcBoolean);
-        } else {
-            $rv[] = new Value(true, Value::$xmlrpcBoolean);
-        }
-    }
-
-    return new PhpXmlRpc\Response($rv);
-}
-
-// Sorting demo
-//
-// send me an array of structs thus:
-//
-// Dave 35
-// Edd  45
-// Fred 23
-// Barney 37
-//
-// and I'll return it to you in sorted order
-
-function agesorter_compare($a, $b)
-{
-    global $agesorter_arr;
-
-    // don't even ask me _why_ these come padded with hyphens, I couldn't tell you :p
-    $a = str_replace("-", "", $a);
-    $b = str_replace("-", "", $b);
-
-    if ($agesorter_arr[$a] == $agesorter_arr[$b]) {
-        return 0;
-    }
-
-    return ($agesorter_arr[$a] > $agesorter_arr[$b]) ? -1 : 1;
-}
-
-$agesorter_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
-$agesorter_doc = 'Send this method an array of [string, int] structs, eg:
-<pre>
- Dave   35
- Edd    45
- Fred   23
- Barney 37
-</pre>
-And the array will be returned with the entries sorted by their numbers.
-';
-function ageSorter($req)
-{
-    global $agesorter_arr, $s;
-
-    PhpXmlRpc\Server::xmlrpc_debugmsg("Entering 'agesorter'");
-    // get the parameter
-    $sno = $req->getParam(0);
-    // error string for [if|when] things go wrong
-    $err = "";
-    $agar = array();
-
-    $max = $sno->count();
-    PhpXmlRpc\Server::xmlrpc_debugmsg("Found $max array elements");
-    foreach ($sno as $i => $rec) {
-        if ($rec->kindOf() != "struct") {
-            $err = "Found non-struct in array at element $i";
-            break;
-        }
-        // extract name and age from struct
-        $n = $rec["name"];
-        $a = $rec["age"];
-        // $n and $a are xmlrpcvals,
-        // so get the scalarval from them
-        $agar[$n->scalarval()] = $a->scalarval();
-    }
-
-    // create the output value
-    $v = new Value(array(), Value::$xmlrpcArray);
-
-    $agesorter_arr = $agar;
-    // hack, must make global as uksort() won't
-    // allow us to pass any other auxiliary information
-    uksort($agesorter_arr, 'agesorter_compare');
-    while (list($key, $val) = each($agesorter_arr)) {
-        // recreate each struct element
-        $v[] = new Value(
-            array(
-                "name" => new Value($key),
-                "age" => new Value($val, "int")
-            ),
-            Value::$xmlrpcStruct
-        );
-    }
-
-    if ($err) {
-        return new PhpXmlRpc\Response(0, PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser, $err);
-    } else {
-        return new PhpXmlRpc\Response($v);
-    }
-}
-
-// signature and instructions, place these in the dispatch map
-$mailsend_sig = array(array(
-    Value::$xmlrpcBoolean, Value::$xmlrpcString, Value::$xmlrpcString,
-    Value::$xmlrpcString, Value::$xmlrpcString, Value::$xmlrpcString,
-    Value::$xmlrpcString, Value::$xmlrpcString,
-));
-$mailsend_doc = 'mail.send(recipient, subject, text, sender, cc, bcc, mimetype)<br/>
-recipient, cc, and bcc are strings, comma-separated lists of email addresses, as described above.<br/>
-subject is a string, the subject of the message.<br/>
-sender is a string, it\'s the email address of the person sending the message. This string can not be
-a comma-separated list, it must contain a single email address only.<br/>
-text is a string, it contains the body of the message.<br/>
-mimetype, a string, is a standard MIME type, for example, text/plain.
-';
-// WARNING; this functionality depends on the sendmail -t option
-// it may not work with Windows machines properly; particularly
-// the Bcc option. Sneak on your friends at your own risk!
-function mailSend($req)
-{
-    $err = "";
-
-    $mTo = $req->getParam(0);
-    $mSub = $req->getParam(1);
-    $mBody = $req->getParam(2);
-    $mFrom = $req->getParam(3);
-    $mCc = $req->getParam(4);
-    $mBcc = $req->getParam(5);
-    $mMime = $req->getParam(6);
-
-    if ($mTo->scalarval() == "") {
-        $err = "Error, no 'To' field specified";
-    }
-
-    if ($mFrom->scalarval() == "") {
-        $err = "Error, no 'From' field specified";
-    }
-
-    $msgHdr = "From: " . $mFrom->scalarval() . "\n";
-    $msgHdr .= "To: " . $mTo->scalarval() . "\n";
-
-    if ($mCc->scalarval() != "") {
-        $msgHdr .= "Cc: " . $mCc->scalarval() . "\n";
-    }
-    if ($mBcc->scalarval() != "") {
-        $msgHdr .= "Bcc: " . $mBcc->scalarval() . "\n";
-    }
-    if ($mMime->scalarval() != "") {
-        $msgHdr .= "Content-type: " . $mMime->scalarval() . "\n";
-    }
-    $msgHdr .= "X-Mailer: XML-RPC for PHP mailer 1.0";
-
-    if ($err == "") {
-        if (!mail("",
-            $mSub->scalarval(),
-            $mBody->scalarval(),
-            $msgHdr)
-        ) {
-            $err = "Error, could not send the mail.";
-        }
-    }
-
-    if ($err) {
-        return new PhpXmlRpc\Response(0, PhpXmlRpc\PhpXmlRpc::$xmlrpcerruser, $err);
-    } else {
-        return new PhpXmlRpc\Response(new Value(true, Value::$xmlrpcBoolean));
-    }
-}
-
-$getallheaders_sig = array(array(Value::$xmlrpcStruct));
-$getallheaders_doc = 'Returns a struct containing all the HTTP headers received with the request. Provides limited functionality with IIS';
-function getAllHeaders_xmlrpc($req)
-{
-    $encoder = new PhpXmlRpc\Encoder();
-
-    if (function_exists('getallheaders')) {
-        return new PhpXmlRpc\Response($encoder->encode(getallheaders()));
-    } else {
-        $headers = array();
-        // IIS: poor man's version of getallheaders
-        foreach ($_SERVER as $key => $val) {
-            if (strpos($key, 'HTTP_') === 0) {
-                $key = ucfirst(str_replace('_', '-', strtolower(substr($key, 5))));
-                $headers[$key] = $val;
-            }
-        }
-
-        return new PhpXmlRpc\Response($encoder->encode($headers));
-    }
-}
-
-$setcookies_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcStruct));
-$setcookies_doc = 'Sends to client a response containing a single \'1\' digit, and sets to it http cookies as received in the request (array of structs describing a cookie)';
-function setCookies($req)
-{
-    $encoder = new PhpXmlRpc\Encoder();
-    $cookies = $req->getParam(0);
-    foreach ($cookies as $name => $value) {
-        $cookieDesc = $encoder->decode($value);
-        setcookie($name, @$cookieDesc['value'], @$cookieDesc['expires'], @$cookieDesc['path'], @$cookieDesc['domain'], @$cookieDesc['secure']);
-    }
-
-    return new PhpXmlRpc\Response(new Value(1, Value::$xmlrpcInt));
-}
-
-$getcookies_sig = array(array(Value::$xmlrpcStruct));
-$getcookies_doc = 'Sends to client a response containing all http cookies as received in the request (as struct)';
-function getCookies($req)
-{
-    $encoder = new PhpXmlRpc\Encoder();
-    return new PhpXmlRpc\Response($encoder->encode($_COOKIE));
-}
-
-$v1_arrayOfStructs_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcArray));
-$v1_arrayOfStructs_doc = 'This handler takes a single parameter, an array of structs, each of which contains at least three elements named moe, larry and curly, all <i4>s. Your handler must add all the struct elements named curly and return the result.';
-function v1_arrayOfStructs($req)
-{
-    $sno = $req->getParam(0);
-    $numCurly = 0;
-    foreach ($sno as $str) {
-        foreach ($str as $key => $val) {
-            if ($key == "curly") {
-                $numCurly += $val->scalarval();
-            }
-        }
-    }
-
-    return new PhpXmlRpc\Response(new Value($numCurly, Value::$xmlrpcInt));
-}
-
-$v1_easyStruct_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcStruct));
-$v1_easyStruct_doc = 'This handler takes a single parameter, a struct, containing at least three elements named moe, larry and curly, all &lt;i4&gt;s. Your handler must add the three numbers and return the result.';
-function v1_easyStruct($req)
-{
-    $sno = $req->getParam(0);
-    $moe = $sno["moe"];
-    $larry = $sno["larry"];
-    $curly = $sno["curly"];
-    $num = $moe->scalarval() + $larry->scalarval() + $curly->scalarval();
-
-    return new PhpXmlRpc\Response(new Value($num, Value::$xmlrpcInt));
-}
-
-$v1_echoStruct_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcStruct));
-$v1_echoStruct_doc = 'This handler takes a single parameter, a struct. Your handler must return the struct.';
-function v1_echoStruct($req)
-{
-    $sno = $req->getParam(0);
-
-    return new PhpXmlRpc\Response($sno);
-}
-
-$v1_manyTypes_sig = array(array(
-    Value::$xmlrpcArray, Value::$xmlrpcInt, Value::$xmlrpcBoolean,
-    Value::$xmlrpcString, Value::$xmlrpcDouble, Value::$xmlrpcDateTime,
-    Value::$xmlrpcBase64,
-));
-$v1_manyTypes_doc = 'This handler takes six parameters, and returns an array containing all the parameters.';
-function v1_manyTypes($req)
-{
-    return new PhpXmlRpc\Response(new Value(
-        array(
-            $req->getParam(0),
-            $req->getParam(1),
-            $req->getParam(2),
-            $req->getParam(3),
-            $req->getParam(4),
-            $req->getParam(5)
-        ),
-        Value::$xmlrpcArray
-    ));
-}
-
-$v1_moderateSizeArrayCheck_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcArray));
-$v1_moderateSizeArrayCheck_doc = 'This handler takes a single parameter, which is an array containing between 100 and 200 elements. Each of the items is a string, your handler must return a string containing the concatenated text of the first and last elements.';
-function v1_moderateSizeArrayCheck($req)
-{
-    $ar = $req->getParam(0);
-    $sz = $ar->count();
-    $first = $ar[0];
-    $last = $ar[$sz - 1];
-
-    return new PhpXmlRpc\Response(new Value($first->scalarval() .
-        $last->scalarval(), Value::$xmlrpcString));
-}
-
-$v1_simpleStructReturn_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcInt));
-$v1_simpleStructReturn_doc = 'This handler takes one parameter, and returns a struct containing three elements, times10, times100 and times1000, the result of multiplying the number by 10, 100 and 1000.';
-function v1_simpleStructReturn($req)
-{
-    $sno = $req->getParam(0);
-    $v = $sno->scalarval();
-
-    return new PhpXmlRpc\Response(new Value(
-        array(
-            "times10" => new Value($v * 10, Value::$xmlrpcInt),
-            "times100" => new Value($v * 100, Value::$xmlrpcInt),
-            "times1000" => new Value($v * 1000, Value::$xmlrpcInt)
-        ),
-        Value::$xmlrpcStruct
-    ));
-}
-
-$v1_nestedStruct_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcStruct));
-$v1_nestedStruct_doc = 'This handler takes a single parameter, a struct, that models a daily calendar. At the top level, there is one struct for each year. Each year is broken down into months, and months into days. Most of the days are empty in the struct you receive, but the entry for April 1, 2000 contains a least three elements named moe, larry and curly, all &lt;i4&gt;s. Your handler must add the three numbers and return the result.';
-function v1_nestedStruct($req)
-{
-    $sno = $req->getParam(0);
-
-    $twoK = $sno["2000"];
-    $april = $twoK["04"];
-    $fools = $april["01"];
-    $curly = $fools["curly"];
-    $larry = $fools["larry"];
-    $moe = $fools["moe"];
-
-    return new PhpXmlRpc\Response(new Value($curly->scalarval() + $larry->scalarval() + $moe->scalarval(), Value::$xmlrpcInt));
-}
-
-$v1_countTheEntities_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcString));
-$v1_countTheEntities_doc = 'This handler takes a single parameter, a string, that contains any number of predefined entities, namely &lt;, &gt;, &amp; \' and ".<BR>Your handler must return a struct that contains five fields, all numbers: ctLeftAngleBrackets, ctRightAngleBrackets, ctAmpersands, ctApostrophes, ctQuotes.';
-function v1_countTheEntities($req)
-{
-    $sno = $req->getParam(0);
-    $str = $sno->scalarval();
-    $gt = 0;
-    $lt = 0;
-    $ap = 0;
-    $qu = 0;
-    $amp = 0;
-    for ($i = 0; $i < strlen($str); $i++) {
-        $c = substr($str, $i, 1);
-        switch ($c) {
-            case ">":
-                $gt++;
-                break;
-            case "<":
-                $lt++;
-                break;
-            case "\"":
-                $qu++;
-                break;
-            case "'":
-                $ap++;
-                break;
-            case "&":
-                $amp++;
-                break;
-            default:
-                break;
-        }
-    }
-
-    return new PhpXmlRpc\Response(new Value(
-        array(
-            "ctLeftAngleBrackets" => new Value($lt, Value::$xmlrpcInt),
-            "ctRightAngleBrackets" => new Value($gt, Value::$xmlrpcInt),
-            "ctAmpersands" => new Value($amp, Value::$xmlrpcInt),
-            "ctApostrophes" => new Value($ap, Value::$xmlrpcInt),
-            "ctQuotes" => new Value($qu, Value::$xmlrpcInt)
-        ),
-        Value::$xmlrpcStruct
-    ));
-}
-
-// trivial interop tests
-// http://www.xmlrpc.com/stories/storyReader$1636
-
-$i_echoString_sig = array(array(Value::$xmlrpcString, Value::$xmlrpcString));
-$i_echoString_doc = "Echoes string.";
-
-$i_echoStringArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
-$i_echoStringArray_doc = "Echoes string array.";
-
-$i_echoInteger_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcInt));
-$i_echoInteger_doc = "Echoes integer.";
-
-$i_echoIntegerArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
-$i_echoIntegerArray_doc = "Echoes integer array.";
-
-$i_echoFloat_sig = array(array(Value::$xmlrpcDouble, Value::$xmlrpcDouble));
-$i_echoFloat_doc = "Echoes float.";
-
-$i_echoFloatArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
-$i_echoFloatArray_doc = "Echoes float array.";
-
-$i_echoStruct_sig = array(array(Value::$xmlrpcStruct, Value::$xmlrpcStruct));
-$i_echoStruct_doc = "Echoes struct.";
-
-$i_echoStructArray_sig = array(array(Value::$xmlrpcArray, Value::$xmlrpcArray));
-$i_echoStructArray_doc = "Echoes struct array.";
-
-$i_echoValue_doc = "Echoes any value back.";
-$i_echoValue_sig = array(array(Value::$xmlrpcValue, Value::$xmlrpcValue));
-
-$i_echoBase64_sig = array(array(Value::$xmlrpcBase64, Value::$xmlrpcBase64));
-$i_echoBase64_doc = "Echoes base64.";
-
-$i_echoDate_sig = array(array(Value::$xmlrpcDateTime, Value::$xmlrpcDateTime));
-$i_echoDate_doc = "Echoes dateTime.";
-
-function i_echoParam($req)
-{
-    $s = $req->getParam(0);
-
-    return new PhpXmlRpc\Response($s);
-}
-
-function i_echoString($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoInteger($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoFloat($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoStruct($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoStringArray($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoIntegerArray($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoFloatArray($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoStructArray($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoValue($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoBase64($req)
-{
-    return i_echoParam($req);
-}
-
-function i_echoDate($req)
-{
-    return i_echoParam($req);
-}
-
-$i_whichToolkit_sig = array(array(Value::$xmlrpcStruct));
-$i_whichToolkit_doc = "Returns a struct containing the following strings: toolkitDocsUrl, toolkitName, toolkitVersion, toolkitOperatingSystem.";
-
-function i_whichToolkit($req)
-{
-    global $SERVER_SOFTWARE;
-    $ret = array(
-        "toolkitDocsUrl" => "http://phpxmlrpc.sourceforge.net/",
-        "toolkitName" => PhpXmlRpc\PhpXmlRpc::$xmlrpcName,
-        "toolkitVersion" => PhpXmlRpc\PhpXmlRpc::$xmlrpcVersion,
-        "toolkitOperatingSystem" => isset($SERVER_SOFTWARE) ? $SERVER_SOFTWARE : $_SERVER['SERVER_SOFTWARE'],
-    );
-
-    $encoder = new PhpXmlRpc\Encoder();
-    return new PhpXmlRpc\Response($encoder->encode($ret));
+        return new Response(new Value(plain_findstate($a->scalarval())));
 }
 
 $object = new xmlrpcServerMethodsContainer();
+
 $signatures = array(
-    "examples.getStateName" => array(
-        "function" => "findState",
-        "signature" => $findstate_sig,
-        "docstring" => $findstate_doc,
-    ),
-    "examples.sortByAge" => array(
-        "function" => "ageSorter",
-        "signature" => $agesorter_sig,
-        "docstring" => $agesorter_doc,
-    ),
-    "examples.addtwo" => array(
-        "function" => "addTwo",
-        "signature" => $addtwo_sig,
-        "docstring" => $addtwo_doc,
-    ),
-    "examples.addtwodouble" => array(
-        "function" => "addTwoDouble",
-        "signature" => $addtwodouble_sig,
-        "docstring" => $addtwodouble_doc,
-    ),
-    "examples.stringecho" => array(
-        "function" => "stringEcho",
-        "signature" => $stringecho_sig,
-        "docstring" => $stringecho_doc,
-    ),
-    "examples.echo" => array(
-        "function" => "echoBack",
-        "signature" => $echoback_sig,
-        "docstring" => $echoback_doc,
-    ),
-    "examples.decode64" => array(
-        "function" => "echoSixtyFour",
-        "signature" => $echosixtyfour_sig,
-        "docstring" => $echosixtyfour_doc,
-    ),
-    "examples.invertBooleans" => array(
-        "function" => "bitFlipper",
-        "signature" => $bitflipper_sig,
-        "docstring" => $bitflipper_doc,
-    ),
+
     // signature omitted on purpose
     "tests.generatePHPWarning" => array(
         "function" => array($object, "phpWarningGenerator"),
@@ -810,172 +70,54 @@ $signatures = array(
         "signature" => $stringecho_sig,
         "docstring" => $stringecho_doc,
     ),*/
-    "examples.getallheaders" => array(
-        "function" => 'getAllHeaders_xmlrpc',
-        "signature" => $getallheaders_sig,
-        "docstring" => $getallheaders_doc,
-    ),
-    "examples.setcookies" => array(
-        "function" => 'setCookies',
-        "signature" => $setcookies_sig,
-        "docstring" => $setcookies_doc,
-    ),
-    "examples.getcookies" => array(
-        "function" => 'getCookies',
-        "signature" => $getcookies_sig,
-        "docstring" => $getcookies_doc,
-    ),
-    "mail.send" => array(
-        "function" => "mailSend",
-        "signature" => $mailsend_sig,
-        "docstring" => $mailsend_doc,
-    ),
-    "validator1.arrayOfStructsTest" => array(
-        "function" => "v1_arrayOfStructs",
-        "signature" => $v1_arrayOfStructs_sig,
-        "docstring" => $v1_arrayOfStructs_doc,
-    ),
-    "validator1.easyStructTest" => array(
-        "function" => "v1_easyStruct",
-        "signature" => $v1_easyStruct_sig,
-        "docstring" => $v1_easyStruct_doc,
-    ),
-    "validator1.echoStructTest" => array(
-        "function" => "v1_echoStruct",
-        "signature" => $v1_echoStruct_sig,
-        "docstring" => $v1_echoStruct_doc,
-    ),
-    "validator1.manyTypesTest" => array(
-        "function" => "v1_manyTypes",
-        "signature" => $v1_manyTypes_sig,
-        "docstring" => $v1_manyTypes_doc,
-    ),
-    "validator1.moderateSizeArrayCheck" => array(
-        "function" => "v1_moderateSizeArrayCheck",
-        "signature" => $v1_moderateSizeArrayCheck_sig,
-        "docstring" => $v1_moderateSizeArrayCheck_doc,
-    ),
-    "validator1.simpleStructReturnTest" => array(
-        "function" => "v1_simpleStructReturn",
-        "signature" => $v1_simpleStructReturn_sig,
-        "docstring" => $v1_simpleStructReturn_doc,
-    ),
-    "validator1.nestedStructTest" => array(
-        "function" => "v1_nestedStruct",
-        "signature" => $v1_nestedStruct_sig,
-        "docstring" => $v1_nestedStruct_doc,
-    ),
-    "validator1.countTheEntities" => array(
-        "function" => "v1_countTheEntities",
-        "signature" => $v1_countTheEntities_sig,
-        "docstring" => $v1_countTheEntities_doc,
-    ),
-    "interopEchoTests.echoString" => array(
-        "function" => "i_echoString",
-        "signature" => $i_echoString_sig,
-        "docstring" => $i_echoString_doc,
-    ),
-    "interopEchoTests.echoStringArray" => array(
-        "function" => "i_echoStringArray",
-        "signature" => $i_echoStringArray_sig,
-        "docstring" => $i_echoStringArray_doc,
-    ),
-    "interopEchoTests.echoInteger" => array(
-        "function" => "i_echoInteger",
-        "signature" => $i_echoInteger_sig,
-        "docstring" => $i_echoInteger_doc,
-    ),
-    "interopEchoTests.echoIntegerArray" => array(
-        "function" => "i_echoIntegerArray",
-        "signature" => $i_echoIntegerArray_sig,
-        "docstring" => $i_echoIntegerArray_doc,
-    ),
-    "interopEchoTests.echoFloat" => array(
-        "function" => "i_echoFloat",
-        "signature" => $i_echoFloat_sig,
-        "docstring" => $i_echoFloat_doc,
-    ),
-    "interopEchoTests.echoFloatArray" => array(
-        "function" => "i_echoFloatArray",
-        "signature" => $i_echoFloatArray_sig,
-        "docstring" => $i_echoFloatArray_doc,
-    ),
-    "interopEchoTests.echoStruct" => array(
-        "function" => "i_echoStruct",
-        "signature" => $i_echoStruct_sig,
-        "docstring" => $i_echoStruct_doc,
-    ),
-    "interopEchoTests.echoStructArray" => array(
-        "function" => "i_echoStructArray",
-        "signature" => $i_echoStructArray_sig,
-        "docstring" => $i_echoStructArray_doc,
-    ),
-    "interopEchoTests.echoValue" => array(
-        "function" => "i_echoValue",
-        "signature" => $i_echoValue_sig,
-        "docstring" => $i_echoValue_doc,
-    ),
-    "interopEchoTests.echoBase64" => array(
-        "function" => "i_echoBase64",
-        "signature" => $i_echoBase64_sig,
-        "docstring" => $i_echoBase64_doc,
-    ),
-    "interopEchoTests.echoDate" => array(
-        "function" => "i_echoDate",
-        "signature" => $i_echoDate_sig,
-        "docstring" => $i_echoDate_doc,
-    ),
-    "interopEchoTests.whichToolkit" => array(
-        "function" => "i_whichToolkit",
-        "signature" => $i_whichToolkit_sig,
-        "docstring" => $i_whichToolkit_doc,
-    ),
-
-    'tests.getStateName.2' => $findstate2_sig,
-    'tests.getStateName.3' => $findstate3_sig,
-    'tests.getStateName.4' => $findstate4_sig,
-    'tests.getStateName.5' => $findstate5_sig,
-    'tests.getStateName.6' => $findstate6_sig,
-    'tests.getStateName.7' => $findstate7_sig,
-    'tests.getStateName.8' => $findstate8_sig,
-    'tests.getStateName.9' => $findstate9_sig,
-    'tests.getStateName.10' => $findstate10_sig,
-    'tests.getStateName.11' => $findstate11_sig,
 
     'tests.getStateName.12' => array(
         "function" => "findStateWithNulls",
         "signature" => $findstate12_sig,
         "docstring" => $findstate_doc,
     ),
-
-    'tests.returnPhpObject' => $returnObj_sig,
 );
 
-$signatures = array_merge($signatures, $moreSignatures);
+$signatures = array_merge($signatures, $signatures1, $signatures2, $signatures3, $signatures4);
 
-// enable support for the NULL extension
-PhpXmlRpc\PhpXmlRpc::$xmlrpc_null_extension = true;
+// Enable support for the NULL extension
+PhpXmlRpc::$xmlrpc_null_extension = true;
 
-$s = new PhpXmlRpc\Server($signatures, false);
-$s->setdebug(3);
+$s = new Server($signatures, false);
+$s->setDebug(3);
 $s->compress_response = true;
 
-// out-of-band information: let the client manipulate the server operations.
-// we do this to help the testsuite script: do not reproduce in production!
+// Out-of-band information: let the client manipulate the server operations.
+// We do this to help the testsuite script: do not reproduce in production!
 if (isset($_GET['RESPONSE_ENCODING'])) {
     $s->response_charset_encoding = $_GET['RESPONSE_ENCODING'];
 }
 if (isset($_GET['DETECT_ENCODINGS'])) {
-    PhpXmlRpc\PhpXmlRpc::$xmlrpc_detectencodings = $_GET['DETECT_ENCODINGS'];
+    PhpXmlRpc::$xmlrpc_detectencodings = $_GET['DETECT_ENCODINGS'];
 }
 if (isset($_GET['EXCEPTION_HANDLING'])) {
     $s->exception_handling = $_GET['EXCEPTION_HANDLING'];
 }
+if (isset($_GET['FORCE_AUTH'])) {
+    // We implement both  Basic and Digest auth in php to avoid having to set it up in a vhost.
+    // Code taken from php.net
+    // NB: we do NOT check for valid credentials!
+    if ($_GET['FORCE_AUTH'] == 'Basic') {
+        if (!isset($_SERVER['PHP_AUTH_USER']) && !isset($_SERVER['REMOTE_USER']) && !isset($_SERVER['REDIRECT_REMOTE_USER'])) {
+            header('HTTP/1.0 401 Unauthorized');
+            header('WWW-Authenticate: Basic realm="Phpxmlrpc Basic Realm"');
+            die('Text visible if user hits Cancel button');
+        }
+    } elseif ($_GET['FORCE_AUTH'] == 'Digest') {
+        if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
+            header('HTTP/1.1 401 Unauthorized');
+            header('WWW-Authenticate: Digest realm="Phpxmlrpc Digest Realm",qop="auth",nonce="'.uniqid().'",opaque="'.md5('Phpxmlrpc Digest Realm').'"');
+            die('Text visible if user hits Cancel button');
+        }
+    }
+}
+
 $s->service();
-// that should do all we need!
+// That should do all we need!
 
-// out-of-band information: let the client manipulate the server operations.
-// we do this to help the testsuite script: do not reproduce in production!
-if (isset($_COOKIE['PHPUNIT_SELENIUM_TEST_ID']) && extension_loaded('xdebug')) {
-    include_once __DIR__ . "/../../vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumCommon/append.php";
-}
+require_once __DIR__ . "/_append.php";
index 3c9812a..70436ff 100644 (file)
@@ -1,10 +1,8 @@
-<html>
+<?php require_once __DIR__ . "/client/_prepend.php"; ?><html lang="en">
 <head><title>xmlrpc</title></head>
 <body>
 <?php
 
-include_once __DIR__ . "/../vendor/autoload.php";
-
 $req = new PhpXmlRpc\Request('examples.getStateName');
 
 print "<h3>Testing value serialization</h3>\n";
@@ -86,10 +84,10 @@ print "Or in UTC, that is " . PhpXmlRpc\Helper\Date::iso8601Encode($t, 1) . "\n"
 $tb = PhpXmlRpc\Helper\Date::iso8601Decode($date);
 print "That is to say $date --> $tb\n";
 print "Which comes out at " . PhpXmlRpc\Helper\Date::iso8601Encode($tb) . "\n";
-print "Which was the time in UTC at " . PhpXmlRpc\Helper\Date::iso8601Encode($date, 1) . "\n";
+print "Which was the time in UTC at " . PhpXmlRpc\Helper\Date::iso8601Encode($tb, 1) . "\n";
 
 print "</pre>\n";
 
 ?>
 </body>
-</html>
+</html><?php require_once __DIR__ . "/client/_append.php"; ?>
similarity index 99%
rename from php/phpxmlrpc/ChangeLog
rename to php/phpxmlrpc/doc/ChangeLog
index e032f79..afb9b81 100644 (file)
@@ -1,6 +1,5 @@
-NB: All recent commits are available online.
+NB: This is an old changelog. All recent commits are available online at https://github.com/gggeek/phpxmlrpc/commits
 This file will not be updated further.
-See https://github.com/gggeek/phpxmlrpc/commits/master
 
 2014-05-26 - G. Giunta (giunta.gaetano@gmail.com)
 
@@ -966,7 +965,7 @@ See https://github.com/gggeek/phpxmlrpc/commits/master
 
        * xmlrpc.inc: fixed php_xmlrpc_encode detection of php arrays (again!);
        removed from wrap_php_function the part about setting a custom error handler
-       (it can be activated using the more general $server->setdebug(3) anyway)
+       (it can be activated using the more general $server->setDebug(3) anyway)
 
        * xmlrpcs.inc: added to server the capability to trap all processing errors
        during execution of user functions and add them to debug info inside responses;
diff --git a/php/phpxmlrpc/doc/build/composer.json b/php/phpxmlrpc/doc/build/composer.json
new file mode 100644 (file)
index 0000000..f75f150
--- /dev/null
@@ -0,0 +1,23 @@
+{
+    "name": "phpxmlrpc/phpxmlrpc-doc-toolchain",
+    "require": {
+        "php": "^5.3.0 || ^7.0 || ^8.0",
+        "ext-xsl": "*",
+        "indeyets/pake": "^1.99",
+        "docbook/docbook-xsl": "^1.79",
+        "phpdocumentor/phpdocumentor": "^2.9.1 || ^3.1.2"
+    },
+    "repositories": [
+        {
+            "type": "package",
+            "package": {
+                "name": "docbook/docbook-xsl",
+                "version": "1.79.2",
+                "dist": {
+                    "url": "https://github.com/docbook/xslt10-stylesheets/releases/download/release/1.79.2/docbook-xsl-1.79.2.zip",
+                    "type": "zip"
+                }
+            }
+        }
+    ]
+}
diff --git a/php/phpxmlrpc/doc/build/setup_tools.sh b/php/phpxmlrpc/doc/build/setup_tools.sh
new file mode 100644 (file)
index 0000000..6a6bb4a
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# Has to be run as a sudoer
+
+set -e
+
+sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
+    asciidoctor fop git unzip zip
+
+PHPPKG=$(dpkg --list | grep php | grep cli | awk '{print $2}')
+sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "${PHPPKG/cli/xsl}"
+
+cd "$(dirname -- $(dirname -- $(dirname -- ${BASH_SOURCE[0]})))"
+if [ ! -d build/tools ]; then
+    mkdir build/tools
+fi
+if [ -L "$(pwd)/build/tools/composer.json" ]; then
+    rm "$(pwd)/build/tools/composer.json"
+fi
+ln -s $(pwd)/doc/build/composer.json $(pwd)/build/tools/composer.json
+cd build/tools
+# in case we are switching between php versions, aleways reinstall every tool with the corect version...
+if [ -f composer.lock ]; then
+    rm composer.lock
+fi
+composer install --no-dev
+# required as of phpdoc 3.1.2
+sed -r -i -e "s|resource: '%kernel\\.project_dir%/vendor/phpdocumentor/reflection/src/phpDocumentor/Reflection/Php'|resource: '%kernel.project_dir%/../reflection/src/phpDocumentor/Reflection/Php'|g" ./vendor/phpdocumentor/phpdocumentor/config/reflection.yaml
index 63ac427..585951c 100644 (file)
@@ -17,14 +17,11 @@ Main goals of the project are ease of use, flexibility and completeness.
 
 The original author is Edd Dumbill of link:$$http://usefulinc.com/$$[Useful Information Company]. As of the 1.0 stable
     release, the project was opened to wider involvement and moved to
-    link:$$http://phpxmlrpc.sourceforge.net/$$[SourceForge]; later, to link:$$https://github.com/gggeek/phpxmlrpc$$[Github]
+    link:$$https://sourceforge.net/projects/phpxmlrpc/$$[SourceForge]; later, to link:$$https://github.com/gggeek/phpxmlrpc$$[Github]
 
 XML-RPC is a format devised by link:$$http://www.userland.com/$$[Userland Software] for achieving remote procedure call
     via XML using HTTP as the transport. XML-RPC has its own web site, link:$$http://www.xmlrpc.com/$$[www.xmlrpc.com]
 
-A list of XML-RPC implementations for other languages such as Perl and Python can be found on the
-    link:$$http://www.xmlrpc.com/$$[www.xmlrpc.com] site.
-
 === Acknowledgements
 
 Daniel E. Baumann
@@ -53,9 +50,9 @@ Peter Kocks
 
 Daniel Krippner
 
-{empty}S. Kuip
+S. Kuip
 
-{empty}A. Lambert
+A. Lambert
 
 Frederic Lecointre
 
@@ -131,25 +128,18 @@ debugger/*:: a graphical debugger which can be used to test calls to xmlrpc serv
 
 demo/*:: example code for implementing both xmlrpc client and server functionality
 
-doc/*:: the documentation/ this manual, and the list of API changes between versions 3 and 4
-
-extras/rsakey.pem:: A test certificate key for the SSL support, which can be used to generate dummy certificates. It has
-    the passphrase "test."
+doc/*:: the documentation, including this manual, and the list of API changes between versions 3 and 4
 
-extras/test.pl, extras/test.py:: Perl and Python programs to exercise server.php to test that some of the methods work.
-
-extras/workspace.testPhpServer.fttb:: Frontier scripts to exercise the demo server. Thanks to Dave Winer for permission
-    to include these. See link:$$http://www.xmlrpc.com/discuss/msgReader$853$$[Dave's announcement of these.]
+extras/*:: php utility scripts, such as a benchmark suite and an evironment compatibility checker
 
 lib/*:: a compatibility layer for applications which still rely on version 3 of the API
 
 src/*:: the XML-RPC library classes. You can autoload these via Composer, or via a dedicated Autoloader class
 
-tests/*:: the test suite for the library, written using PhpUnit, and the configuration to run it on Travis
+tests/*:: the test suite for the library, written using PhpUnit, and the configuration to run it either on GitHub Actions or in a local Docker container
 
 
 [[bugs]]
-
 == Known Bugs
 
 Known bugs are tracked using the link:$$https://github.com/gggeek/phpxmlrpc/issues$$[GitHub issue tracker]
@@ -172,15 +162,10 @@ Very little HTTP response checking is performed (e.g. HTTP redirects are not fol
     header, mandated by the xml-rpc spec, is not validated); cookie support still involves quite a bit of coding on the
     part of the user.
 
-Support for receiving from servers version 1 cookies (i.e. conforming to RFC 2965) is quite incomplete, and might cause
-    unforeseen errors.
-
 
 [[support]]
-
 == Support
 
-
 === Online Support
 
 XML-RPC for PHP is offered "as-is" without any warranty or commitment to support. However, informal advice and help is
@@ -191,31 +176,28 @@ XML-RPC for PHP is offered "as-is" without any warranty or commitment to support
     posted to the link:$$https://github.com/gggeek/phpxmlrpc/issues$$[project's website].
 
 * The __PHP XML-RPC interest mailing list__ is run by the original author. More details
-    link:$$http://lists.gnomehack.com/mailman/listinfo/phpxmlrpc$$[can be found here].
+    link:$$https://lists.usefulinc.com/cgi-bin/mailman/listinfo/phpxmlrpc$$[can be found here].
 
 
 [[jellyfish]]
-
 === The Jellyfish Book
 
 image::progxmlrpc.s.gif[The Jellyfish Book]
 Together with Simon St.Laurent and Joe Johnston, Edd Dumbill wrote a book on XML-RPC for O'Reilly and Associates on
     XML-RPC. It features a rather fetching jellyfish on the cover.
 
-Complete details of the book are link:$$http://www.oreilly.com/catalog/progxmlrpc/$$[available from O'Reilly's web site.]
+Complete details of the book are link:$$https://www.oreilly.com/library/view/programming-web-services/0596001193/$$[available from O'Reilly's web site.]
 
 Edd is responsible for the chapter on PHP, which includes a worked example of creating a forum server, and hooking it up
-    the O'Reilly's link:$$http://meerkat.oreillynet.com/$$[Meerkat] service in order to allow commenting on news stories
+    the (now discontinued) O'Reilly's Meerkat service in order to allow commenting on news stories
     from around the Web.
 
 If you've benefited from the effort that has been put into writing this software, then please consider buying the book!
 
 
 [[apidocs]]
-
 == Class documentation
 
-
 ==== Notes on types
 
 ===== int
@@ -265,7 +247,6 @@ There is no support for encoding ++null++
           them, and uses the same encoding convention (see ...).
 
 [[xmlrpcval-creation]]
-
 ==== Xmlrpcval creation
 
 The constructor is the normal way to create an
@@ -294,12 +275,10 @@ Examples:
 
 [source, php]
 ----
-
 $myInt = new xmlrpcval(1267, "int");
 $myString = new xmlrpcval("Hello, World!", "string");
 $myBool = new xmlrpcval(1, "boolean");
 $myString2 = new xmlrpcval(1.24, "string"); // note: this will serialize a php float value as xmlrpc string
-
 ----
 
 The fourth constructor form can be used to compose complex
@@ -315,7 +294,6 @@ Examples:
 
 [source, php]
 ----
-
 $myArray = new xmlrpcval(
   array(
     new xmlrpcval("Tom"),
@@ -337,14 +315,12 @@ $myStruct = new xmlrpcval(
       "struct")
   ),
   "struct");
-
 ----
 
 See the file ++vardemo.php++ in this distribution
         for more examples.
 
 [[xmlrpc-client]]
-
 ==== Xmlrpc-client creation
 
 The constructor accepts one of two possible syntaxes:
@@ -356,10 +332,8 @@ xmlrpc_clientnew
 
 [source, php]
 ----
-
 $client = new xmlrpc_client("http://phpxmlrpc.sourceforge.net/server.php");
 $another_client = new xmlrpc_client("https://james:bond@secret.service.com:443/xmlrpcserver?agent=007");
-
 ----
 
 The second syntax does not allow to express a username and
@@ -372,15 +346,12 @@ Here's another example client set up to query Userland's XML-RPC
 
 [source, php]
 ----
-
 $client = new xmlrpc_client("/RPC2", "betty.userland.com", 80);
-
 ----
 
 The server_port parameter is optional,
         and if omitted will default to 80 when using HTTP and 443 when using
-        HTTPS (see the <<xmlrpc-client-send>> method
-        below).
+        HTTPS.
 
 The transport parameter is optional, and
         if omitted will default to 'http'. Allowed values are either
@@ -402,7 +373,6 @@ The implementation of this class has been kept as simple to use as
 
 [source, php]
 ----
-
   function foo ($xmlrpcmsg) {
     ...
     return new xmlrpcresp($some_xmlrpc_val);
@@ -420,7 +390,6 @@ The implementation of this class has been kept as simple to use as
       "examples.myFunc1" => array("function" => "foo"),
       "examples.myFunc2" => array("function" => "bar::foobar"),
     ));
-
 ----
 
 This performs everything you need to do with a server. The single
@@ -466,7 +435,6 @@ Here is a more detailed example of what the handler function
 
 [source, php]
 ----
-
   function foo ($xmlrpcmsg) {
     global $xmlrpcerruser; // import user errcode base value
 
@@ -485,7 +453,6 @@ Here is a more detailed example of what the handler function
       return new xmlrpcresp(new xmlrpcval("All's fine!", "string"));
     }
   }
-
 ----
 
 See __server.php__ in this distribution for
@@ -548,7 +515,6 @@ Look at the __server.php__ example in the
         distribution to see what a dispatch map looks like.
 
 [[signatures]]
-
 ==== Method signatures
 
 A signature is a description of a method's return type and its
@@ -557,24 +523,24 @@ A signature is a description of a method's return type and its
 Within a server's dispatch map, each method has an array of
         possible signatures. Each signature is an array of types. The first
         entry is the return type. For instance, the method
+
 [source, php]
 ----
 string examples.getStateName(int)
-
 ----
 
  has the signature
+
 [source, php]
 ----
 array($xmlrpcString, $xmlrpcInt)
-
 ----
 
  and, assuming that it is the only possible signature for the
         method, it might be used like this in server creation:
+
 [source, php]
 ----
-
 $findstate_sig = array(array($xmlrpcString, $xmlrpcInt));
 
 $findstate_doc = 'When passed an integer between 1 and 51 returns the
@@ -587,11 +553,8 @@ $s = new xmlrpc_server( array(
     "signature" => $findstate_sig,
     "docstring" => $findstate_doc
   )));
-
 ----
 
-
-
 Note that method signatures do not allow to check nested
         parameters, e.g. the number, names and types of the members of a
         struct param cannot be validated.
@@ -604,10 +567,8 @@ If a method that you want to expose has a definite number of
         used in method signatures as a placeholder for 'any xmlrpc
         type':
 
-
 [source, php]
 ----
-
 $echoback_sig = array(array($xmlrpcValue, $xmlrpcValue));
 
 $findstate_doc = 'Echoes back to the client the received value, regardless of its type';
@@ -618,7 +579,6 @@ $s = new xmlrpc_server( array(
     "signature" => $echoback_sig, // this sig guarantees that the method handler will be called with one and only one parameter
     "docstring" => $echoback_doc
   )));
-
 ----
 
 Methods system.listMethods,
@@ -628,7 +588,6 @@ Methods system.listMethods,
         server, and should not be reimplemented (see Reserved Methods
         below).
 
-
 ==== Delaying the server response
 
 You may want to construct the server, but for some reason not
@@ -641,13 +600,11 @@ You may want to construct the server, but for some reason not
 
 [source, php]
 ----
-
 $s = new xmlrpc_server($myDispMap, 0); // second parameter = 0 prevents automatic servicing of request
 
 // ... some code that does other stuff here
 
 $s->service();
-
 ----
 
 Note that the service method will print
@@ -665,10 +622,8 @@ To prevent the server from sending HTTP headers back to the
 Xmlrpc requests retrieved by other means than HTTP POST bodies
         can also be processed. For example:
 
-
 [source, php]
 ----
-
 $s = new xmlrpc_server(); // not passing a dispatch map prevents automatic servicing of request
 
 // ... some code that does other stuff here, including setting dispatch map into server object
@@ -676,10 +631,8 @@ $s = new xmlrpc_server(); // not passing a dispatch map prevents automatic servi
 $resp = $s->service($xmlrpc_request_body, true); // parse a variable instead of POST body, retrieve response payload
 
 // ... some code that does other stuff with xml response $resp here
-
 ----
 
-
 ==== Modifying the server behaviour
 
 A couple of methods / class variables are available to modify
@@ -811,7 +764,6 @@ In the same spirit of simplification that inspired the
         words:
 [source, php]
 ----
-
   function foo($usr_id, $out_lang='en') {
     global $xmlrpcerruser;
 
@@ -839,7 +791,6 @@ In the same spirit of simplification that inspired the
     ), false);
   $s->functions_parameters_type = 'phpvals';
   $s->service();
-
 ----
 
 There are a few things to keep in mind when using this
@@ -874,7 +825,6 @@ last but not least, the direct parsing of xml to php values is
 
 
 [[globalvars]]
-
 == Global variables
 
 Many global variables are defined in the xmlrpc.inc file. Some of
@@ -900,7 +850,6 @@ For convenience the strings representing the XML-RPC types have
         been encoded as global variables:
 [source, php]
 ----
-
 $xmlrpcI4="i4";
 $xmlrpcI8="i8";
 $xmlrpcInt="int";
@@ -913,7 +862,6 @@ $xmlrpcArray="array";
 $xmlrpcStruct="struct";
 $xmlrpcValue="undefined";
 $xmlrpcNull="null";
-
 ----
 
 ==== $xmlrpcTypes, $xmlrpc_valid_parents, $xmlrpcerr, $xmlrpcstr, $xmlrpcerrxml, $xmlrpc_backslash, $_xh, $xml_iso88591_Entities, $xmlEntities, $xmlrpcs_capabilities
@@ -924,7 +872,6 @@ Reserved for internal usage.
 === Variables whose value can be modified
 
 [[xmlrpc-defencoding]]
-
 ==== xmlrpc_defencoding
 
 $xmlrpc_defencoding"UTF8"This variable defines the character set encoding that will be
@@ -961,13 +908,11 @@ $xmlrpc_internalencoding"ISO-8859-1"This variable defines the character set enco
 
 [source, php]
 ----
-
 <?php
 
 include('xmlrpc.inc');
 $xmlrpc_internalencoding = 'UTF-8'; // this has to be set after the inclusion above
 $v = new xmlrpcval('κόÏ\83με'); // This xmlrpc value will be correctly serialized as the greek word 'kosme'
-
 ----
 
 ==== xmlrpcName
@@ -1009,7 +954,6 @@ When set to ++TRUE++, php NULL values encoded
 
 
 [[helpers]]
-
 == Helper functions
 
 XML-RPC for PHP contains some helper functions which you can use to
@@ -1043,7 +987,6 @@ For more information about dates, see link:$$http://www.uic.edu/year2000/datefmt
       representations: CCYYMMDDTHH:MM:SS.
 
 [[iso8601encode]]
-
 ==== iso8601_encode
 
 stringiso8601_encodestring$time_tint$utc0Returns an ISO 8601 formatted date generated from the UNIX
@@ -1061,7 +1004,6 @@ The included demo program __vardemo.php__
         includes a demonstration of this function.
 
 [[iso8601decode]]
-
 ==== iso8601_decode
 
 intiso8601_decodestring$isoStringint$utc0Returns a UNIX timestamp from an ISO 8601 encoded time and date
@@ -1072,7 +1014,6 @@ intiso8601_decodestring$isoStringint$utc0Returns a UNIX timestamp from an ISO 86
         local timestamp.
 
 [[arrayuse]]
-
 === Easy use with nested PHP values
 
 Dan Libby was kind enough to contribute two helper functions that
@@ -1087,7 +1028,6 @@ Dan Libby was kind enough to contribute two helper functions that
 These functions reside in __xmlrpc.inc__.
 
 [[phpxmlrpcdecode]]
-
 ==== php_xmlrpc_decode
 
 mixedphp_xmlrpc_decodexmlrpcval$xmlrpc_valarray$optionsarrayphp_xmlrpc_decodexmlrpcmsg$xmlrpcmsg_valstring$optionsReturns a native PHP value corresponding to the values found in
@@ -1125,7 +1065,6 @@ ____WARNING__:__ please take
 Example:
 [source, php]
 ----
-
 // wrapper to expose an existing php function as xmlrpc method handler
 function foo_wrapper($m)
 {
@@ -1139,11 +1078,9 @@ $s = new xmlrpc_server(array(
      "function" => "foo_wrapper",
      "signatures" => ...
   )));
-
 ----
 
 [[phpxmlrpcencode]]
-
 ==== php_xmlrpc_encode
 
 xmlrpcvalphp_xmlrpc_encodemixed$phpvalarray$optionsReturns an xmlrpcval object populated with the PHP
@@ -1177,7 +1114,6 @@ The first will enable the creation of 'particular' xmlrpcval
 Example:
 [source, php]
 ----
-
 // the easy way to build a complex xml-rpc struct, showing nested base64 value and datetime values
 $val = php_xmlrpc_encode(array(
   'first struct_element: an int' => 666,
@@ -1185,7 +1121,6 @@ $val = php_xmlrpc_encode(array(
   'third: a base64 element' => new xmlrpcval('hello world', 'base64'),
   'fourth: a datetime' => '20060107T01:53:00'
   ), array('auto_dates'));
-
 ----
 
 ==== php_xmlrpc_decode_xml
@@ -1202,11 +1137,9 @@ The options parameter is optional. If
 Example:
 [source, php]
 ----
-
 $text = '<value><array><data><value>Hello world</value></data></array></value>';
 $val = php_xmlrpc_decode_xml($text);
 if ($val) echo 'Found a value of type '.$val->kindOf(); else echo 'Found invalid xml';
-
 ----
 
 === Automatic conversion of php functions into xmlrpc methods (and vice versa)
@@ -1302,7 +1235,6 @@ Example usage:
 
 [source, php]
 ----
-
 $c = new xmlrpc_client('http://phpxmlrpc.sourceforge.net/server.php');
 
 $function = wrap_xmlrpc_method($client, 'examples.getStateName');
@@ -1320,11 +1252,9 @@ else {
   else
     echo "OK, state nr. $stateno is $statename";
 }
-
 ----
 
 [[wrap_php_function]]
-
 ==== wrap_php_function
 
 arraywrap_php_functionstring$funcnamestring$wrapper_function_namearray$extra_optionsGiven a user-defined PHP function, create a PHP 'wrapper'
@@ -1417,11 +1347,9 @@ $findstate_sig = wrap_php_function('findstate');
 if ($findstate_sig)
   $methods['examples.getStateName'] = $findstate_sig;
 $srv = new xmlrpc_server($methods);
-
 ----
 
 [[deprecated]]
-
 === Functions removed from the library
 
 The following two functions have been deprecated in version 1.1 of
@@ -1441,25 +1369,20 @@ To ease the transition to the new naming scheme and avoid breaking
             constant `XMLRPC_EPI_ENABLED` will be set to
             '1'
 
-
-
 The following documentation is kept for historical
       reference:
 
 [[xmlrpcdecode]]
-
 ==== xmlrpc_decode
 
 mixedx mlrpc_decode xmlrpcval $xmlrpc_val Alias for php_xmlrpc_decode.
 
 [[xmlrpcencode]]
-
 ==== xmlrpc_encode
 
 xmlrpcval xmlrpc_encode mixed $phpvalAlias for php_xmlrpc_encode.
 
 [[debugging]]
-
 === Debugging aids
 
 ==== xmlrpc_debugmsg
@@ -1475,7 +1398,6 @@ Use this function in your methods so you can pass back
 
 
 [[reserved]]
-
 == Reserved methods
 
 In order to extend the functionality offered by XML-RPC servers
@@ -1507,7 +1429,6 @@ The system.listMethods method requires no
       a method implemented by the server.
 
 [[sysmethodsig]]
-
 === system.methodSignature
 
 This method takes one parameter, the name of a method implemented
@@ -1536,19 +1457,16 @@ If no signature is defined for the method, a not-array value is
 
 [source, php]
 ----
-
 $v = $resp->value();
 if ($v->kindOf() != "array") {
   // then the method did not have a signature defined
 }
-
 ----
 
 See the __introspect.php__ demo included in this
       distribution for an example of using this method.
 
 [[sysmethhelp]]
-
 === system.methodHelp
 
 This method takes one parameter, the name of a method implemented
@@ -1575,14 +1493,12 @@ It returns a response of type array, with each value of the array
 
 
 [[examples]]
-
 == Examples
 
 The best examples are to be found in the sample files included with
     the distribution. Some are included here.
 
 [[statename]]
-
 === XML-RPC client: state name query
 
 Code to get the corresponding state name from a number (1-50) from
@@ -1590,7 +1506,6 @@ Code to get the corresponding state name from a number (1-50) from
 
 [source, php]
 ----
-
   $m = new xmlrpcmsg('examples.getStateName',
     array(new xmlrpcval($HTTP_POST_VARS["stateno"], "int")));
   $c = new xmlrpc_client("/server.php", "phpxmlrpc.sourceforge.net", 80);
@@ -1606,7 +1521,6 @@ Code to get the corresponding state name from a number (1-50) from
       print "Code: " . htmlentities($r->faultCode()) . "<BR>" .
             "Reason: '" . htmlentities($r->faultString()) . "'<BR>";
   }
-
 ----
 
 === Executing a multicall call
@@ -1616,7 +1530,7 @@ To be documented...
 
 [[faq]]
 
-[qanda]
+[[qanda]]
 == Frequently Asked Questions
 
 ==== How to send custom XML as payload of a method call::
@@ -1684,7 +1598,7 @@ The most likely cause is that you are not using the correct URL
 
 To find out what the server is really returning to your client,
       you have to enable the debug mode of the client, using
-      $client->setdebug(1);
+      $client->setDebug(1);
 
 
 ==== How can I save to a file the xml of the xmlrpc responses received from servers?
@@ -1697,11 +1611,9 @@ If what you need is to save the responses received from the server
 
 [source, php]
 ----
-
 $resp = $client->send($msg);
 if (!$resp->faultCode())
   $data_to_be_saved = $resp->serialize();
-
 ----
 
 Note that this will not be 100% accurate, since the xml generated
@@ -1719,13 +1631,11 @@ Note that this will not be 100% accurate, since the xml generated
 
 [source, php]
 ----
-
 $client = new xmlrpc_client($url);
 $client->return_type = 'xml';
 $resp = $client->send($msg);
 if (!$resp->faultCode())
   $data_to_be_saved = $resp->value();
-
 ----
 
 Note that using this method the xml response response will not be
@@ -1774,7 +1684,6 @@ The code below uses sessions to e.g. let the client store a value
 
 [source, php]
 ----
-
 $resp = $client->send(new xmlrpcmsg('registervalue', array(new xmlrpcval('foo'), new xmlrpcval('bar'))));
 if (!$resp->faultCode())
 {
@@ -1789,7 +1698,6 @@ if (!$resp->faultCode())
     $val = $client->send(new xmlrpcmsg('getvalue', array(new xmlrpcval('foo')));
   }
 }
-
 ----
 
 Server-side sessions are handled normally like in any other
@@ -1803,7 +1711,6 @@ NB: unlike web browsers, not all xmlrpc clients support usage of
 
 
 [[integration]]
-
 [appendix]
 == Integration with the PHP xmlrpc extension
 
@@ -1816,7 +1723,6 @@ In short: for the fastest execution possible, you can enable the php
 
 [source, php]
 ----
-
 /*** client side ***/
 $c = new xmlrpc_client('http://phpxmlrpc.sourceforge.net/server.php');
 
@@ -1843,12 +1749,9 @@ else
   else
     echo'Got response: '.htmlentities($v);
 }
-
 ----
 
-
 [[substitution]]
-
 [appendix]
 == Substitution of the PHP xmlrpc extension
 
@@ -1867,7 +1770,6 @@ Since version 2.1, the PHP-XMLRPC library provides a compatibility
 
 
 [[enough]]
-
 [appendix]
 == 'Enough of xmlrpcvals!': new style library usage
 
@@ -1879,7 +1781,6 @@ In the meantime, see docs about xmlrpc_client::return_type and
 
 
 [[debugger]]
-
 [appendix]
 == Usage of the debugger
 
@@ -1911,7 +1812,6 @@ The debugger can take advantage of the JSONRPC library extension, to
           either to the same directory as the debugger or somewhere in your
           php include path
 
-
 * to enable the visual value editing dialog, download the
           JS-XMLRPC library, and copy somewhere in the web root files
           __visualeditor.php__,
@@ -1920,11 +1820,8 @@ The debugger can take advantage of the JSONRPC library extension, to
           debugger file __controller.php__ and set
           appropriately the variable $editorpath.
 
-
 [[news]]
-
 [appendix]
-
 == Whats's new
 
 CAUTION: not all items the following list have (yet) been fully documented, and some might not be present in any other
@@ -1959,7 +1856,7 @@ Backward compatibility is maintained via _lib/xmlrpc.inc_, _lib/xmlrpcs.inc_ and
 
 * improved: a specific option allows users to decide the version of SSL to use for https calls.
   This is useful f.e. for the testing suite, when the server target of calls has no proper ssl certificate,
-  and the cURL extension has been compiled with GnuTLS (such as on Travis VMs)
+  and the cURL extension has been compiled with GnuTLS
 
 * improved: the function `wrap_php_function()` now can be used to wrap closures (it is now a method btw)
 
similarity index 91%
rename from php/phpxmlrpc/tests/benchmark.php
rename to php/phpxmlrpc/extras/benchmark.php
index 43cbc42..2f0f55a 100644 (file)
@@ -3,11 +3,13 @@
  * Benchmarking suite for the PHP-XMLRPC lib.
  *
  * @author Gaetano Giunta
- * @copyright (c) 2005-2015 G. Giunta
+ * @copyright (c) 2005-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  *
- * @todo add a test for response ok in call testing
+ * @todo add a check for response ok in call testing
  * @todo add support for --help option to give users the list of supported parameters
+ * @todo make number of test iterations flexible
+ * @todo add https tests
  **/
 
 use PhpXmlRpc\PhpXmlRpc;
@@ -17,9 +19,10 @@ use PhpXmlRpc\Client;
 use PhpXmlRpc\Response;
 use PhpXmlRpc\Encoder;
 
+/// @todo allow autoloading when the library is installed as dependency
 include_once __DIR__ . '/../vendor/autoload.php';
 
-include __DIR__ . '/parse_args.php';
+include __DIR__ . '/../tests/parse_args.php';
 $args = argParser::getArgs();
 
 function begin_test($test_name, $test_case)
@@ -167,7 +170,7 @@ for ($i = 0; $i < $num_tests; $i++) {
     for ($k = 0; $k < $l; $k++) {
         $val1 = $value->arraymem($k);
         $out = array();
-        while (list($name, $val) = $val1->structeach()) {
+        foreach($val1 as $name => $val) {
             $out[$name] = array();
             $m = $val->arraysize();
             for ($j = 0; $j < $m; $j++) {
@@ -198,21 +201,23 @@ if (function_exists('xmlrpc_decode')) {
 
 if (!$xd) {
 
+    $num_tests = 25;
+
     /// test multicall vs. many calls vs. keep-alives
     $encoder = new Encoder();
     $value = $encoder->encode($data1, array('auto_dates'));
     $req = new Request('interopEchoTests.echoValue', array($value));
     $reqs = array();
-    for ($i = 0; $i < 25; $i++) {
+    for ($i = 0; $i < $num_tests; $i++) {
         $reqs[] = $req;
     }
-    $server = explode(':', $args['LOCALSERVER']);
+    $server = explode(':', $args['HTTPSERVER']);
     if (count($server) > 1) {
-        $srv = $server[1] . '://' . $server[0] . $args['URI'];
-        $c = new Client($args['URI'], $server[0], $server[1]);
+        $srv = $server[1] . '://' . $server[0] . $args['HTTPURI'];
+        $c = new Client($args['HTTPURI'], $server[0], $server[1]);
     } else {
-        $srv = $args['LOCALSERVER'] . $args['URI'];
-        $c = new Client($args['URI'], $args['LOCALSERVER']);
+        $srv = $args['HTTPSERVER'] . $args['HTTPURI'];
+        $c = new Client($args['HTTPURI'], $args['HTTPSERVER']);
     }
     // do not interfere with http compression
     $c->accepted_compression = array();
@@ -225,7 +230,7 @@ if (!$xd) {
     }
     begin_test($testName, 'http 10');
     $response = array();
-    for ($i = 0; $i < 25; $i++) {
+    for ($i = 0; $i < $num_tests; $i++) {
         $resp = $c->send($req);
         $response[] = $resp->value();
     }
@@ -234,7 +239,7 @@ if (!$xd) {
     if (function_exists('curl_init')) {
         begin_test($testName, 'http 11 w. keep-alive');
         $response = array();
-        for ($i = 0; $i < 25; $i++) {
+        for ($i = 0; $i < $num_tests; $i++) {
             $resp = $c->send($req, 10, 'http11');
             $response[] = $resp->value();
         }
@@ -243,7 +248,7 @@ if (!$xd) {
         $c->keepalive = false;
         begin_test($testName, 'http 11');
         $response = array();
-        for ($i = 0; $i < 25; $i++) {
+        for ($i = 0; $i < $num_tests; $i++) {
             $resp = $c->send($req, 10, 'http11');
             $response[] = $resp->value();
         }
@@ -263,7 +268,7 @@ if (!$xd) {
 
         begin_test($testName, 'http 10 w. compression');
         $response = array();
-        for ($i = 0; $i < 25; $i++) {
+        for ($i = 0; $i < $num_tests; $i++) {
             $resp = $c->send($req);
             $response[] = $resp->value();
         }
@@ -272,7 +277,7 @@ if (!$xd) {
         if (function_exists('curl_init')) {
             begin_test($testName, 'http 11 w. keep-alive and compression');
             $response = array();
-            for ($i = 0; $i < 25; $i++) {
+            for ($i = 0; $i < $num_tests; $i++) {
                 $resp = $c->send($req, 10, 'http11');
                 $response[] = $resp->value();
             }
@@ -281,7 +286,7 @@ if (!$xd) {
             $c->keepalive = false;
             begin_test($testName, 'http 11 w. compression');
             $response = array();
-            for ($i = 0; $i < 25; $i++) {
+            for ($i = 0; $i < $num_tests; $i++) {
                 $resp = $c->send($req, 10, 'http11');
                 $response[] = $resp->value();
             }
diff --git a/php/phpxmlrpc/extras/rsakey.pem b/php/phpxmlrpc/extras/rsakey.pem
deleted file mode 100644 (file)
index 6c51248..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
------BEGIN RSA PRIVATE KEY-----\r
-MIIBOgIBAAJBAM12w6/J20HMj0V9VC24xPFQG9RKSDt8vmviM+tnc1BgCrzPyF1v\r
-3/rWGoWDjkJrE9WFOeqIjJHeEWWT4uKq2ZkCAwEAAQJAZZYJ7Nld+et9DvuHak/H\r
-uBRGnjDYA+mKcObXitWMUzk2ZodL8UoCP1J9kKqV8Zp/l2cBZkLo0aWTN94sWkHy\r
-rQIhAOhxWxRXSZ4kArIQqZnDG9JgtOAeaaFso/zpxIHpN6OrAiEA4klzl+rUc32/\r
-7SDcJYa9j5vehp1jCTnkN+n0rujTM8sCIAGwMRUovSQk5tAcRt8TB7SzdxzZm7LM\r
-czR3DjJTW1AZAiEAlYN+svPgJ+cAdwdtLgZXHZoZb8xx8Vik6CTXHPKNCf0CIBQL\r
-zF4Qp8/C+gjsXtEZJvhxY7i1luHn6iNwNnE932r3\r
------END RSA PRIVATE KEY-----\r
diff --git a/php/phpxmlrpc/extras/test.pl b/php/phpxmlrpc/extras/test.pl
deleted file mode 100644 (file)
index 6b3cec0..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/local/bin/perl\r
-\r
-use Frontier::Client;\r
-\r
-my $serverURL='http://phpxmlrpc.sourceforge.net/server.php';\r
-\r
-# try the simplest example\r
-\r
-my $client = Frontier::Client->new( 'url' => $serverURL,\r
-               'debug' => 0, 'encoding' => 'iso-8859-1' );\r
-my $resp = $client->call("examples.getStateName", 32);\r
-\r
-print "Got '${resp}'\n";\r
-\r
-# now send a mail to nobody in particular\r
-\r
-$resp = $client->call("mail.send", ("edd", "Test",  \r
-       "Bonjour. Je m'appelle Gérard. Mañana. ", "freddy", "", "", \r
-       'text/plain; charset="iso-8859-1"'));\r
-\r
-if ($resp->value()) {\r
-       print "Mail sent OK.\n";\r
-} else {\r
-       print "Error sending mail.\n";\r
-}\r
-\r
-# test echoing of characters works fine\r
-\r
-$resp = $client->call("examples.echo", 'Three "blind" mice - ' . \r
-       "See 'how' they run");\r
-print $resp . "\n";\r
-\r
-# test name and age example. this exercises structs and arrays \r
-\r
-$resp = $client->call("examples.sortByAge", \r
-                                                                                       [ { 'name' => 'Dave', 'age' => 35},\r
-                                                                                               { 'name' => 'Edd', 'age' => 45 },\r
-                                                                                               { 'name' => 'Fred', 'age' => 23 },\r
-                                                                                               { 'name' => 'Barney', 'age' => 36 } ] );\r
-\r
-my $e;\r
-foreach $e (@$resp) {\r
-       print $$e{'name'} . ", " . $$e{'age'} . "\n";\r
-}\r
-\r
-# test base64\r
-\r
-$resp = $client->call("examples.decode64", \r
-                                                                                       $client->base64("TWFyeSBoYWQgYSBsaXR0bGUgbGFtYiBTaGUgd" .\r
-                                                                                                                                                       "GllZCBpdCB0byBhIHB5bG9u"));\r
-\r
-print $resp . "\n";\r
diff --git a/php/phpxmlrpc/extras/test.py b/php/phpxmlrpc/extras/test.py
deleted file mode 100644 (file)
index adc0aea..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/local/bin/python\r
-\r
-from xmlrpclib import *\r
-import sys\r
-\r
-server = Server("http://phpxmlrpc.sourceforge.net/server.php")\r
-\r
-try:\r
-    print "Got '" + server.examples.getStateName(32) + "'"\r
-\r
-    r = server.mail.send("edd", "Test",\r
-                         "Bonjour. Je m'appelle Gérard. Mañana. ", "freddy", "", "", \r
-                         'text/plain; charset="iso-8859-1"')\r
-    if r:\r
-        print "Mail sent OK"\r
-    else:\r
-        print "Error sending mail"\r
-\r
-\r
-    r = server.examples.echo('Three "blind" mice - ' + "See 'how' they run")\r
-    print r\r
-\r
-    # name/age example. this exercises structs and arrays\r
-\r
-    a = [ {'name': 'Dave', 'age': 35}, {'name': 'Edd', 'age': 45 },\r
-          {'name': 'Fred', 'age': 23}, {'name': 'Barney', 'age': 36 }]\r
-    r = server.examples.sortByAge(a)\r
-    print r\r
-\r
-    # test base 64\r
-    b = Binary("Mary had a little lamb She tied it to a pylon")\r
-    b.encode(sys.stdout)\r
-    r = server.examples.decode64(b)\r
-    print r\r
-    \r
-except Error, v:\r
-    print "XML-RPC Error:",v\r
similarity index 98%
rename from php/phpxmlrpc/tests/verify_compat.php
rename to php/phpxmlrpc/extras/verify_compat.php
index 1d9f90c..98edae9 100644 (file)
@@ -3,7 +3,7 @@
  * Verify compatibility level of current php install with php-xmlrpc lib.
  *
  * @author Gaetano Giunta
- * @copyright (C) 2006-2015 G. Giunta
+ * @copyright (C) 2006-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  *
  * @todo add a test for php output buffering?
@@ -36,7 +36,7 @@ function phpxmlrpc_verify_compat($mode = 'client')
             $tests['zlib']['description'] = "The zlib extension is enabled.\n\nYou will be able to receive compressed requests and send compressed responses for the 'HTTP' protocol";
         }
 
-        // test for dispaly of php errors in xml reponse
+        // test for display of php errors in xml response
         if (ini_get('display_errors')) {
             if (intval(ini_get('error_reporting')) && E_NOTICE) {
                 $tests['display_errors']['status'] = 1;
diff --git a/php/phpxmlrpc/extras/workspace.testPhpServer.fttb b/php/phpxmlrpc/extras/workspace.testPhpServer.fttb
deleted file mode 100644 (file)
index 8d50758..0000000
+++ /dev/null
@@ -1 +0,0 @@
-\r<!--\r#fatPage\r#version 1\r#docs http://www.scripting.com/fatPages/faq.html\r#adrPageData workspace.testPhpServer\r#objectType application/x-frontier-tabl\r#runnable false\r#pageData AAEDAAAADxcAAABMAAMAALU6OiG1Oj+vAAAAAAAAAAANAAAAAAUAAAGDDQAAAAGQAAAC4w0AAAAC7AAABecNAAAABe4AAAskDQAAAAsrAAALpg0AAAALsARlY2hvAAABegABBAIAAgAAABIAAADsAAAAAgAPBUFyaWFsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAZUAAC1OjqKtTo+ZAAAAAYAAADoAN0C4AP3TEFORP///////wAAAAAAAAB3aW4AAAAAAAAAAAAAAAB3aXRoIHdvcmtzcGFjZS50ZXN0UGhwU2VydmVyLnNlcnZlcg0JbG9jYWwgKHBhcmFtcyA9IHsib2ggdGhlIGJ1enppbmcgb2YgdGhlIGJlZXMifSkNCXdwLm5ld3RleHRvYmplY3QgKGJldHR5LnJwYy5jbGllbnQgKGRvbWFpbiwgcG9ydCwgImV4YW1wbGVzLmVjaG8iLCBAcGFyYW1zLCBycGNwYXRoOnBhdGgsIGZsU2hvd01lc3NhZ2VzOiBmYWxzZSwgZmxkZWJ1ZzpmYWxzZSksIEBzY3JhdGNocGFkLnBocFRleHQpDYAAAAAAAIAAAAAAAIAAAAAAAAxnZXRTdGF0ZU5hbWUAAAFPAAEEAgACAAAAGAAAALsAAwACAA8FQXJpYWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAABlQAALU6Ooq1OjuFAAAAAgAAAaAAogKQAy5MQU5E////////AAAAAAAAAHdpbgAAAAAAAAAAAAAAAHdpdGggd29ya3NwYWNlLnRlc3RQaHBTZXJ2ZXIuc2VydmVyDQlmb3IgaSA9IDEgdG8gNTANCQlsb2NhbCAocGFyYW1zID0ge2l9KQ0JCW1zZyAoYmV0dHkucnBjLmNsaWVudCAoZG9tYWluLCBwb3J0LCAiZXhhbXBsZXMuZ2V0U3RhdGVOYW1lIiwgQHBhcmFtcywgcnBjcGF0aDpwYXRoLCBmbFNob3dNZXNzYWdlczogZmFsc2UpKQ2AAAAAAACAAAAAAACAAAAAAACAAAAAAAAIbWFpbFNlbmQAAAL3AAEEAgACAAAAMAAAAksAAwACAA8FQXJpYWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAABlQAALU6Pqm1Oj90AAAAAQAAAK4ADgLDAzZMQU5E////////AAAAAAAAAHdpbgAAAAAAAAAAAAAAAHdpdGggd29ya3NwYWNlLnRlc3RQaHBTZXJ2ZXIuc2VydmVyDQlsb2NhbCAocmVjaXBpZW50ID0gImRhdmVAdXNlcmxhbmQuY29tIikNCWxvY2FsIChzdWJqZWN0ID0gIkhpIERhdmUhIikNCWxvY2FsICh0ZXh0ID0gIlRoaXMgaXMgYSBtZXNzYWdlIHNlbnQgYnkgYSBzY3JpcHQgb24gIiArIHRjcC5kbnMuZ2V0TXlEb21haW5OYW1lICgpICsgIiBzZW50IHRocm91Z2ggYSBQSFAgc2VydmVyIHJ1bm5pbmcgb24gIiArIGRvbWFpbiArICIgYXQgIiArIGNsb2NrLm5vdyAoKSArICIuIikNCWxvY2FsIChzZW5kZXIgPSB1c2VyLnByZWZzLm1haWxhZGRyZXNzKQ0JbG9jYWwgKGNjID0gIiIsIGJjYyA9ICIiLCBtaW1ldHlwZSA9ICJ0ZXh0L3BsYWluIikNCWxvY2FsIChwYXJhbXMgPSB7cmVjaXBpZW50LCBzdWJqZWN0LCB0ZXh0LCBzZW5kZXIsIGNjLCBiY2MsIG1pbWV0eXBlfSkNCXdwLm5ld3RleHRvYmplY3QgKGJldHR5LnJwYy5jbGllbnQgKGRvbWFpbiwgcG9ydCwgIm1haWwuc2VuZCIsIEBwYXJhbXMsIHJwY3BhdGg6cGF0aCwgZmxTaG93TWVzc2FnZXM6IGZhbHNlLCBmbGRlYnVnOmZhbHNlKSwgQHNjcmF0Y2hwYWQucGhwVGV4dCkNgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAAgAAAAAAABnJlYWRtZQAABTIAAQECRkZGRjAwMDAwMDA2MDAwMTAwMTcBNzdBQQMwMDAwMDAwMDAwNUIwMDAwMDAwMAIxMDAxNwIxMDECODA0OTAwMgIxODAwMDACMIKCAjYwMDA2MAIwAjMCOTMBMjABMIGBAi0xggIwwggCM4ICOTMCMjFGAUZGRkaBgQEwgoICMQIwwgQCQjMBMAMwMDAxMDAwMDAwMzYwMDAwMDAwMQIwAkIzATQwODQCMIGCgYICM4ICOTMCMjFGATlCATABMwE4RTAwATACODMCMwI5MwI5OQIwAzAwMDIwMDAwMDBCODAwMDAwMDAwAEIzLFRoZXNlIHNjcmlwdHMgdGVzdCB0aGUgcHVibGljIGhhbmRsZXJzIGF0Og0NaHR0cDovL3htbHJwYy51c2VmdWxpbmMuY29tL2RlbW8vaW50cm9zcGVjdC5waHANDURvY3VtZW50YXRpb24gaXMgaGVyZToNDWh0dHA6Ly94bWxycGMudXNlZnVsaW5jLmNvbS9waHAuaHRtbA0NNS82LzAwOyA5OjMyOjA1IFBNIGJ5IERXAzAwMDQwMDAwMDAwOTAwMDAwMDAyAjCBAkI1ATADMDAwNTAwMDAwMDA5MDAwMDAwMDICMIECQjUBMAMwMDA2MDAwMDAwMzEwMDAwMDAwMQEwwQMBRAEzATCBgoLBBAFGRkZGgYEBMIICQTAwMDACMMIKAjICMMIHwSCCgsEDggMwMDA3MDAwMDAwMTkwMDAwMDAwMQEwgYHCD4GCAjICMMIHgQIxOAEwgsEDggMwMDA4MDAwMDAwQUEwMDAwMDAwMQA0MCwNTVMgU2FucyBTZXJpZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQwLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNAEwgYECNEU0AjQwMAIwAjECMgIxggIwwgMCMTAxATADMDAwOTAwMDAwMDE1MDAwMDAwMDICMIICOTkCMTdBAjCCAjk5AjE3QQMwMDBBMDAwMDAwMTUwMDAwMDAwMgIzggI5MwIyMUYCM4ICOTMCMjFGAzAwMEIwMDAwMDAwNTAwMDAwMDAyAjDCBwMwMDBDMDAwMDAwMTAwMDAwMDAwMQJCMwIwwgMBOAI5NoIBMAMwMDBGMDAwMDAwNTIwMDAwMDAwMAI0MAEwwgsBMQIwwgmBgYKBgcIFwQXCA8EEgoLBA8IeADQsAAAAAAA0LHUAAAAANCxSAGUAADQsAgAhAAA0LAE3N0EANCwAAAAAADQsNCzYAAMwMDEzMDAwMDAwN0UwMDAwMDAwMAFEAUEBOQExRgE4AUMBRQExQwExRAExRQExRgE3RgExQgEwADQ0LAEtAAABLgAAAbYAAAG1AAABlQAAAbUAAAGlAAABpQAAAS4AAAEnAAABIgAAAZEAAAGSAAABkwAAAZQAAAGFAAABgwAAAUZGRkaBgQEwAUZGRkaBgQEwA0ZGRkUwMDAwMDAwNjAwMDEwMDE3ATc3QUEDAAG1Oj+vtTo/1QAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACzAAAAAAAAAAAAAAEsAL8B1QJJAAAAAAAABKoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnNlcnZlcgAAAHcAAQMCAAAAbwAAAC4AAwAAtTo6TLU6PHkAAAAAAAAAAAwAAAAABwAAAB8MAAAAACQAAAA4AwAAAABQBmRvbWFpbgAAABR4bWxycGMudXNlZnVsaW5jLmNvbQRwYXRoAAAAEC9kZW1vL3NlcnZlci5waHAEcG9ydAlzb3J0QnlBZ2UAAAMTAAEEAgACAAAAcgAAAiUAEgACAA8FQXJpYWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAABlQAALU6Ooq1Oj3dAAAABQAAAIgAGQKGAzlMQU5E////////AAAAAAAAAHdpbgAAAAAAAAAAAAAAAHdpdGggd29ya3NwYWNlLnRlc3RQaHBTZXJ2ZXIuc2VydmVyDQlsb2NhbCAobGlzdHRvYmVzb3J0ZWQgPSB7fSwgc29ydGVkbGlzdCkNCWJ1bmRsZSAvL2J1aWxkIHRoZSBsaXN0DQkJb24gYWRkIChuYW1lLCBhZ2UpDQkJCWxvY2FsICh0KQ0JCQluZXcgKHRhYmxldHlwZSwgQHQpDQkJCXQubmFtZSA9IG5hbWUNCQkJdC5hZ2UgPSBhZ2UNCQkJbGlzdHRvYmVzb3J0ZWQgPSBsaXN0dG9iZXNvcnRlZCArIHt0fQ0JCWFkZCAoIkRhdmUiLCAzNSkNCQlhZGQgKCJFZGQiLCA0NSkNCQlhZGQgKCJGcmVkIiwgMjMpDQkJYWRkICgiQmFybmV5IiwgMzcpDQlsb2NhbCAocGFyYW1zID0ge2xpc3R0b2Jlc29ydGVkfSkNCXNvcnRlZGxpc3QgPSBiZXR0eS5ycGMuY2xpZW50IChkb21haW4sIHBvcnQsICJleGFtcGxlcy5zb3J0QnlBZ2UiLCBAcGFyYW1zLCBycGNwYXRoOnBhdGgsIGZsU2hvd01lc3NhZ2VzOiBmYWxzZSwgZmxkZWJ1ZzpmYWxzZSkNCQ0JbG9jYWwgKGl0ZW0pDQlmb3IgaXRlbSBpbiBzb3J0ZWRsaXN0DQkJbXNnIChpdGVtLm5hbWUgKyAiLCAiICsgaXRlbS5hZ2UpDYAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAIAAAAAAAAAQAFYNTVMgU2FucyBTZXJpZgAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAwAAALEAzQBIALwA8wG8AucBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAEOAAAARwAGAAIADw1NUyBTYW5zIFNlcmlmAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAtTo6RLU6P6sAAAAEAAABYAGIAigDCUxBTkT///////8AAAAAAAAAd2luAAAAAAAAAAAAAAAAZWNobw1nZXRTdGF0ZU5hbWUNcmVhZG1lDW1haWxTZW5kDXNlcnZlcg0JZG9tYWluDQlwb3J0DQlwYXRoDXNvcnRCeUFnZQ2AAAAAABgAAHgb/wA4ZRwBAAAAAAAAAAAAAAAAAACAAAAAABgAAHgb/wA4ZRwBAAAAAAAAAAAAAAAAAACAAAAAABgAAHgb/wA4ZRwBAAAAAAAAAAAAAAAAAACAAAAAABgAAHgb/wA4ZRwBAAAAAAAAAAAAAAAAAACAAAAAABgBAHgb/wA4ZRwBAAABAAAAAAAAAAAAAACAAAAAABgAAHgb/wBYSgECAAAAAAAAAAAAAAAAAACAAAAAABgAAHgb/wBYSgECAAAAAAAAAAAAAAAAAACAAAAAABgAAHgb/wBYSgECAAAAAAAAAAAAAAAAAACAAAAAABgAAHgb/wA4ZRwBAAAAAAAAAAAAAAAAAAA=\r-->\r
\ No newline at end of file
index 28b47d3..e9338a2 100644 (file)
  * This file is only used to insure backwards compatibility
  * with the API of the library <= rev. 3
  *
- * If it is included, the library will work without any further autoloading
+ * If it is included, the library will work without any further autoloading.
+ *
+ * NB: including this file will also alter the library configuration setting the
+ * expected charset encoding used by the app to ISO-8859-1. Please see the
+ * file api_changes_v4.md for how to change this if required.
  *****************************************************************************/
 
+include_once(__DIR__.'/../src/Client.php');
+include_once(__DIR__.'/../src/Encoder.php');
 include_once(__DIR__.'/../src/PhpXmlRpc.php');
-include_once(__DIR__.'/../src/Value.php');
 include_once(__DIR__.'/../src/Request.php');
 include_once(__DIR__.'/../src/Response.php');
-include_once(__DIR__.'/../src/Client.php');
-include_once(__DIR__.'/../src/Encoder.php');
+include_once(__DIR__.'/../src/Value.php');
+include_once(__DIR__.'/../src/Exception/HttpException.php');
+include_once(__DIR__.'/../src/Exception/PhpXmlrpcException.php');
 include_once(__DIR__.'/../src/Helper/Charset.php');
 include_once(__DIR__.'/../src/Helper/Date.php');
 include_once(__DIR__.'/../src/Helper/Http.php');
 include_once(__DIR__.'/../src/Helper/Logger.php');
 include_once(__DIR__.'/../src/Helper/XMLParser.php');
 
+use PhpXmlRpc\Client;
+use PhpXmlRpc\Encoder;
+use PhpXmlRpc\Request;
+use PhpXmlRpc\Response;
+use PhpXmlRpc\Value;
+use PhpXmlRpc\Helper\Charset;
+use PhpXmlRpc\Helper\Date;
+use PhpXmlRpc\Helper\Http;
+use PhpXmlRpc\Helper\XMLParser;
 
 /* Expose the global variables which used to be defined */
 PhpXmlRpc\PhpXmlRpc::$xmlrpc_internalencoding = 'ISO-8859-1'; // old default
@@ -79,7 +94,10 @@ $GLOBALS['xmlrpc_backslash'] = chr(92).chr(92);
 
 /* Expose with the old names the classes which have been namespaced */
 
-class xmlrpcval extends PhpXmlRpc\Value
+/**
+ * @todo reinstate access to method serializedata ?
+ */
+class xmlrpcval extends Value
 {
     /**
      * @deprecated
@@ -92,8 +110,8 @@ class xmlrpcval extends PhpXmlRpc\Value
         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
         //{
         $ar = $o->me;
-        reset($ar);
-        list($typ, $val) = each($ar);
+        $val = reset($ar);
+        $typ = key($ar);
 
         return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
         //}
@@ -106,16 +124,15 @@ class xmlrpcval extends PhpXmlRpc\Value
     public function getval()
     {
         // UNSTABLE
-        reset($this->me);
-        list($a, $b) = each($this->me);
+        $b = reset($this->me);
+        $a = key($this->me);
         // contributed by I Sofer, 2001-03-24
         // add support for nested arrays to scalarval
         // i've created a new method here, so as to
         // preserve back compatibility
 
         if (is_array($b)) {
-            @reset($b);
-            while (list($id, $cont) = @each($b)) {
+            foreach($b as $id => $cont) {
                 $b[$id] = $cont->scalarval();
             }
         }
@@ -123,12 +140,10 @@ class xmlrpcval extends PhpXmlRpc\Value
         // add support for structures directly encoding php objects
         if (is_object($b)) {
             $t = get_object_vars($b);
-            @reset($t);
-            while (list($id, $cont) = @each($t)) {
+            foreach($t as $id => $cont) {
                 $t[$id] = $cont->scalarval();
             }
-            @reset($t);
-            while (list($id, $cont) = @each($t)) {
+            foreach($t as $id => $cont) {
                 @$b->$id = $cont;
             }
         }
@@ -143,20 +158,27 @@ class xmlrpcval extends PhpXmlRpc\Value
     }
 
     /// reset functionality added by parent class: same as it would happen if no interface was declared
-    public function getIterator() {
+    public function getIterator()
+    {
         return new ArrayIterator($this);
     }
 }
 
-class xmlrpcmsg extends PhpXmlRpc\Request
+/**
+ * @todo reinstate access to method parseResponseHeaders ?
+ */
+class xmlrpcmsg extends Request
 {
 }
 
-class xmlrpcresp extends PhpXmlRpc\Response
+class xmlrpcresp extends Response
 {
 }
 
-class xmlrpc_client extends PhpXmlRpc\Client
+/**
+ * @todo reinstate access to methods sendPayloadHTTP10, sendPayloadHTTPS, sendPayloadCURL, _try_multicall ?
+ */
+class xmlrpc_client extends Client
 {
 }
 
@@ -165,53 +187,53 @@ class xmlrpc_client extends PhpXmlRpc\Client
 /// Wrong speling, but we are adamant on backwards compatibility!
 function xmlrpc_encode_entitites($data, $srcEncoding='', $destEncoding='')
 {
-    return PhpXmlRpc\Helper\Charset::instance()->encodeEntitites($data, $srcEncoding, $destEncoding);
+    return Charset::instance()->encodeEntities($data, $srcEncoding, $destEncoding);
 }
 
 function iso8601_encode($timeT, $utc=0)
 {
-    return PhpXmlRpc\Helper\Date::iso8601Encode($timeT, $utc);
+    return Date::iso8601Encode($timeT, $utc);
 }
 
 function iso8601_decode($iDate, $utc=0)
 {
-    return PhpXmlRpc\Helper\Date::iso8601Decode($iDate, $utc);
+    return Date::iso8601Decode($iDate, $utc);
 }
 
 function decode_chunked($buffer)
 {
-    return PhpXmlRpc\Helper\Http::decodeChunked($buffer);
+    return Http::decodeChunked($buffer);
 }
 
 function php_xmlrpc_decode($xmlrpcVal, $options=array())
 {
-    $encoder = new PhpXmlRpc\Encoder();
+    $encoder = new Encoder();
     return $encoder->decode($xmlrpcVal, $options);
 }
 
 function php_xmlrpc_encode($phpVal, $options=array())
 {
-    $encoder = new PhpXmlRpc\Encoder();
+    $encoder = new Encoder();
     return $encoder->encode($phpVal, $options);
 }
 
 function php_xmlrpc_decode_xml($xmlVal, $options=array())
 {
-    $encoder = new PhpXmlRpc\Encoder();
+    $encoder = new Encoder();
     return $encoder->decodeXml($xmlVal, $options);
 }
 
 function guess_encoding($httpHeader='', $xmlChunk='', $encodingPrefs=null)
 {
-    return PhpXmlRpc\Helper\XMLParser::guessEncoding($httpHeader, $xmlChunk, $encodingPrefs);
+    return XMLParser::guessEncoding($httpHeader, $xmlChunk, $encodingPrefs);
 }
 
 function has_encoding($xmlChunk)
 {
-    return PhpXmlRpc\Helper\XMLParser::hasEncoding($xmlChunk);
+    return XMLParser::hasEncoding($xmlChunk);
 }
 
 function is_valid_charset($encoding, $validList)
 {
-    return PhpXmlRpc\Helper\Charset::instance()->isValidCharset($encoding, $validList);
+    return Charset::instance()->isValidCharset($encoding, $validList);
 }
index cec3374..4331c36 100644 (file)
@@ -87,7 +87,7 @@ function wrap_php_class($className, $extraOptions=array())
  * @see PhpXmlRpc\Wrapper::wrapXmlrpcMethod
  * @param xmlrpc_client $client
  * @param string $methodName
- * @param int|array $extraOptions the usage of an int as signature number is deprecated, use an option in $extraOptions
+ * @param int|array $extraOptions the usage of an int as signature number is deprecated, use an option 'signum' in $extraOptions
  * @param int $timeout            deprecated, use an option in $extraOptions
  * @param string $protocol        deprecated, use an option in $extraOptions
  * @param string $newFuncName     deprecated, use an option in $extraOptions
@@ -112,8 +112,10 @@ function wrap_xmlrpc_method($client, $methodName, $extraOptions=0, $timeout=0, $
         // backwards compat: return string instead of callable
         $extraOptions['return_source'] = true;
         $wrapped = $wrapper->wrapXmlrpcMethod($client, $methodName, $extraOptions);
-        eval($wrapped['source']);
-        $wrapped = $wrapped['function'];
+        if (is_array($wrapped)) {
+            eval($wrapped['source']);
+            $wrapped = $wrapped['function'];
+        }
     } else {
         $wrapped = $wrapper->wrapXmlrpcMethod($client, $methodName, $extraOptions);
     }
index 71cde1f..33043f1 100644 (file)
@@ -44,7 +44,9 @@
 
 include_once(__DIR__.'/../src/Server.php');
 
-class xmlrpc_server extends PhpXmlRpc\Server
+use PhpXmlRpc\Server;
+
+class xmlrpc_server extends Server
 {
     /**
      * A debugging routine: just echoes back the input packet as a string value
@@ -52,41 +54,67 @@ class xmlrpc_server extends PhpXmlRpc\Server
      */
     public function echoInput()
     {
-        $r = new Response(new PhpXmlRpc\Value("'Aha said I: '" . file_get_contents('php://input'), 'string'));
+        $r = new PhpXmlRpc\Response(new PhpXmlRpc\Value("'Aha said I: '" . file_get_contents('php://input'), 'string'));
         print $r->serialize();
     }
+
+    /**
+     * Reinstate access to class members which became protected/private
+     * @param string $name
+     * @return mixed
+     */
+    public function __get($name)
+    {
+        switch($name) {
+            case 'dmap':
+                return $this->dmap;
+            default:
+                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' .
+                    $trace[0]['line'], E_USER_NOTICE);
+                return null;
+        }
+    }
+
+    /**
+     * @param string $name
+     * @return bool
+     */
+    public function __isset($name)
+    {
+        return $name === 'dmap';
+    }
 }
 
 /* Expose as global functions the ones which are now class methods */
 
 /**
- * @see PhpXmlRpc\Server::xmlrpc_debugmsg
+ * @see Server::xmlrpc_debugmsg
  * @param string $m
  */
 function xmlrpc_debugmsg($m)
 {
-    PhpXmlRpc\Server::xmlrpc_debugmsg($m);
+    Server::xmlrpc_debugmsg($m);
 }
 
 function _xmlrpcs_getCapabilities($server, $m=null)
 {
-    return PhpXmlRpc\Server::_xmlrpcs_getCapabilities($server, $m);
+    return Server::_xmlrpcs_getCapabilities($server, $m);
 }
 
-$_xmlrpcs_listMethods_sig=array(array($GLOBALS['xmlrpcArray']));
+$_xmlrpcs_listMethods_sig=array(array(\PhpXmlRpc\Value::$xmlrpcArray));
 $_xmlrpcs_listMethods_doc='This method lists all the methods that the XML-RPC server knows how to dispatch';
 $_xmlrpcs_listMethods_sdoc=array(array('list of method names'));
 function _xmlrpcs_listMethods($server, $m=null) // if called in plain php values mode, second param is missing
 {
-    return PhpXmlRpc\Server::_xmlrpcs_listMethods($server, $m);
+    return Server::_xmlrpcs_listMethods($server, $m);
 }
 
-$_xmlrpcs_methodSignature_sig=array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcString']));
+$_xmlrpcs_methodSignature_sig=array(array(\PhpXmlRpc\Value::$xmlrpcArray, $GLOBALS['xmlrpcString']));
 $_xmlrpcs_methodSignature_doc='Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)';
 $_xmlrpcs_methodSignature_sdoc=array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described'));
 function _xmlrpcs_methodSignature($server, $m)
 {
-    return PhpXmlRpc\Server::_xmlrpcs_methodSignature($server, $m);
+    return Server::_xmlrpcs_methodSignature($server, $m);
 }
 
 $_xmlrpcs_methodHelp_sig=array(array($GLOBALS['xmlrpcString'], $GLOBALS['xmlrpcString']));
@@ -94,28 +122,28 @@ $_xmlrpcs_methodHelp_doc='Returns help text if defined for the method passed, ot
 $_xmlrpcs_methodHelp_sdoc=array(array('method description', 'name of the method to be described'));
 function _xmlrpcs_methodHelp($server, $m)
 {
-    return PhpXmlRpc\Server::_xmlrpcs_methodHelp($server, $m);
+    return Server::_xmlrpcs_methodHelp($server, $m);
 }
 
 function _xmlrpcs_multicall_error($err)
 {
-    return PhpXmlRpc\Server::_xmlrpcs_multicall_error($err);
+    return Server::_xmlrpcs_multicall_error($err);
 }
 
 function _xmlrpcs_multicall_do_call($server, $call)
 {
-    return PhpXmlRpc\Server::_xmlrpcs_multicall_do_call($server, $call);
+    return Server::_xmlrpcs_multicall_do_call($server, $call);
 }
 
 function _xmlrpcs_multicall_do_call_phpvals($server, $call)
 {
-    return PhpXmlRpc\Server::_xmlrpcs_multicall_do_call_phpvals($server, $call);
+    return Server::_xmlrpcs_multicall_do_call_phpvals($server, $call);
 }
 
-$_xmlrpcs_multicall_sig = array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcArray']));
+$_xmlrpcs_multicall_sig = array(array(\PhpXmlRpc\Value::$xmlrpcArray, \PhpXmlRpc\Value::$xmlrpcArray));
 $_xmlrpcs_multicall_doc = 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details';
 $_xmlrpcs_multicall_sdoc = array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"'));
 function _xmlrpcs_multicall($server, $m)
 {
-    return PhpXmlRpc\Server::_xmlrpcs_multicall($server, $m);
+    return Server::_xmlrpcs_multicall($server, $m);
 }
index 4bca828..a0aaa6b 100644 (file)
@@ -3,7 +3,7 @@
  * Makefile for phpxmlrpc library.
  * To be used with the Pake tool: https://github.com/indeyets/pake/wiki
  *
- * @copyright (c) 2015 G. Giunta
+ * @copyright (c) 2015-2021 G. Giunta
  *
  * @todo !important allow user to specify location of docbook xslt instead of the one installed via composer
  */
@@ -42,6 +42,11 @@ class Builder
         return self::buildDir().'/workspace';
     }
 
+    public static function toolsDir()
+    {
+        return self::buildDir().'/tools';
+    }
+
     /// most likely things will break if this one is moved outside of BuildDir
     public static function distDir()
     {
@@ -104,7 +109,6 @@ class Builder
      */
     public static function applyXslt($inFile, $xssFile, $outFileOrDir)
     {
-
         if (!file_exists($inFile)) {
             throw new \Exception("File $inFile cannot be found");
         }
@@ -205,6 +209,7 @@ function run_getopts($task=null, $args=array(), $cliOpts=array())
 
 /**
  * Downloads source code in the build workspace directory, optionally checking out the given branch/tag
+ * @todo allow using current installation as source, bypassing git clone in workspace - at least for doc generation
  */
 function run_init($task=null, $args=array(), $cliOpts=array())
 {
@@ -252,16 +257,20 @@ function run_clean_doc()
  */
 function run_doc($task=null, $args=array(), $cliOpts=array())
 {
+    // in
+    $srcDir = Builder::workspaceDir();
+    // out
     $docDir = Builder::workspaceDir().'/doc';
 
     // API docs
 
     // from phpdoc comments using phpdocumentor
-    //$cmd = Builder::tool('php');
-    //pake_sh("$cmd vendor/phpdocumentor/phpdocumentor/bin/phpdoc run -d ".Builder::workspaceDir().'/src'." -t ".Builder::workspaceDir().'/doc/api --title PHP-XMLRPC');
+    $cmd = Builder::tool('php');
+    pake_sh("$cmd " . Builder::toolsDir(). "/vendor/bin/phpdoc run --cache-folder ".Builder::buildDir()."/.phpdoc -d ".$srcDir.'/src'." -t ".$docDir.'/api --title PHP-XMLRPC');
 
     // from phpdoc comments using Sami
-    $samiConfig = <<<EOT
+    // deprecated on 2021/12, as Sami is abandonware
+    /*$samiConfig = <<<EOT
 <?php
     \$iterator = Symfony\Component\Finder\Finder::create()
       ->files()
@@ -278,18 +287,18 @@ function run_doc($task=null, $args=array(), $cliOpts=array())
 EOT;
     file_put_contents('build/sami_config.php', $samiConfig);
     $cmd = Builder::tool('php');
-    pake_sh("$cmd vendor/sami/sami/sami.php update -vvv build/sami_config.php");
+    pake_sh("$cmd " . Builder::toolsDir(). "/vendor/bin/sami.php update -vvv build/sami_config.php");*/
 
     // User Manual
 
     // html (single file) from asciidoc
     $cmd = Builder::tool('asciidoctor');
-    pake_sh("$cmd -d book $docDir/manual/phpxmlrpc_manual.adoc");
+    pake_sh("$cmd -d book -o $docDir/manual/phpxmlrpc_manual.html $srcDir/doc/manual/phpxmlrpc_manual.adoc");
 
     // then docbook from asciidoc
     /// @todo create phpxmlrpc_manual.xml with the good version number
     /// @todo create phpxmlrpc_manual.xml with the date set to the one of last commit (or today?)
-    pake_sh("$cmd -d book -b docbook $docDir/manual/phpxmlrpc_manual.adoc");
+    pake_sh("$cmd -d book -b docbook -o $docDir/manual/phpxmlrpc_manual.xml $srcDir/doc/manual/phpxmlrpc_manual.adoc");
 
     # Other tools for docbook...
     #
@@ -299,7 +308,7 @@ EOT;
     # convertdoc command for xmlmind xxe editor
     #  convertdoc docb.toHTML xmlrpc_php.xml -u out
     #
-    # saxon + xerces xml parser + saxon extensions + xslthl: adds a little syntax highligting
+    # saxon + xerces xml parser + saxon extensions + xslthl: adds a little syntax highlighting
     # (bold and italics only, no color) for php source examples...
     #  java \
     #  -classpath c:\programmi\saxon\saxon.jar\;c:\programmi\saxon\xslthl.jar\;c:\programmi\xerces\xercesImpl.jar\;C:\htdocs\xmlrpc_cvs\docbook-xsl\extensions\saxon65.jar \
diff --git a/php/phpxmlrpc/phpunit.xml.dist b/php/phpxmlrpc/phpunit.xml.dist
new file mode 100644 (file)
index 0000000..a4e4a19
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<phpunit colors="true">
+
+    <!-- code coverage reporting -->
+    <filter>
+        <whitelist>
+            <directory suffix=".php">./</directory>
+            <exclude>
+                <directory>./build</directory>
+                <directory>./doc</directory>
+                <directory>./extras</directory>
+                <directory>./tests</directory>
+                <directory>./vendor</directory>
+                <file>./pakefile.php</file>
+            </exclude>
+        </whitelist>
+    </filter>
+
+    <testsuites>
+        <testsuite name="PHPXMLRPC Test Suite">
+            <directory>./tests</directory>
+        </testsuite>
+    </testsuites>
+
+</phpunit>
index d31e9f5..cd1491c 100644 (file)
@@ -3,12 +3,18 @@
 namespace PhpXmlRpc;
 
 use PhpXmlRpc\Helper\Logger;
-
+use PhpXmlRpc\Helper\XMLParser;
 /**
  * Used to represent a client of an XML-RPC server.
  */
 class Client
 {
+    const USE_CURL_NEVER = 0;
+    const USE_CURL_ALWAYS = 1;
+    const USE_CURL_AUTO = 2;
+
+    protected static $logger;
+
     /// @todo: do these need to be public?
     public $method = 'http';
     public $server;
@@ -41,6 +47,7 @@ class Client
 
     public $cookies = array();
     public $extracurlopts = array();
+    public $use_curl = self::USE_CURL_AUTO;
 
     /**
      * @var bool
@@ -56,10 +63,9 @@ class Client
      * List of http compression methods accepted by the client for responses.
      * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
      *
-     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
-     * in those cases it will be up to CURL to decide the compression methods
-     * it supports. You might check for the presence of 'zlib' in the output of
-     * curl_version() to determine wheter compression is supported or not
+     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since in those cases it will be up to CURL to
+     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
+     * curl_version() to determine whether compression is supported or not
      */
     public $accepted_compression = array();
 
@@ -67,11 +73,12 @@ class Client
      * Name of compression scheme to be used for sending requests.
      * Either null, gzip or deflate.
      */
-
     public $request_compression = '';
+
     /**
      * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
      * http://curl.haxx.se/docs/faq.html#7.3).
+     * @internal
      */
     public $xmlrpc_curl_handle = null;
 
@@ -83,10 +90,11 @@ class Client
 
     /**
      * The charset encoding that will be used for serializing request sent by the client.
-     * It defaults to NULL, which means using US-ASCII and encoding all characters outside of the ASCII range using
-     * their xml character entity representation (this has the benefit that line end characters will not be mangled in
-     * the transfer, a CR-LF will be preserved as well as a singe LF).
-     *  Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'
+     * It defaults to NULL, which means using US-ASCII and encoding all characters outside of the ASCII printable range
+     * using their xml character entity representation (this has the benefit that line end characters will not be mangled
+     * in the transfer, a CR-LF will be preserved as well as a singe LF).
+     * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
+     * For the fastest mode of operation, set your both your app internal encoding as well as this to UTF-8.
      */
     public $request_charset_encoding = '';
 
@@ -103,13 +111,26 @@ class Client
      * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
      * server as an xmlrpc string or base64 value.
      */
-    public $return_type = 'xmlrpcvals';
+    public $return_type = XMLParser::RETURN_XMLRPCVALS;
 
     /**
      * Sent to servers in http headers.
      */
     public $user_agent;
 
+    public function getLogger()
+    {
+        if (self::$logger === null) {
+            self::$logger = Logger::instance();
+        }
+        return self::$logger;
+    }
+
+    public static function setLogger($logger)
+    {
+        self::$logger = $logger;
+    }
+
     /**
      * @param string $path either the PATH part of the xmlrpc server URL, or complete server URL (in which case you
      *                     should use and empty string for all other parameters)
@@ -125,7 +146,7 @@ class Client
     public function __construct($path, $server = '', $port = '', $method = '')
     {
         // allow user to specify all params in $path
-        if ($server == '' and $port == '' and $method == '') {
+        if ($server == '' && $port == '' && $method == '') {
             $parts = parse_url($path);
             $server = $parts['host'];
             $path = isset($parts['path']) ? $parts['path'] : '';
@@ -163,7 +184,7 @@ class Client
 
         // if ZLIB is enabled, let the client by default accept compressed responses
         if (function_exists('gzinflate') || (
-                function_exists('curl_init') && (($info = curl_version()) &&
+                function_exists('curl_version') && (($info = curl_version()) &&
                     ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
             )
         ) {
@@ -177,7 +198,7 @@ class Client
         $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
 
         // Add all charsets which mbstring can handle, but remove junk not found in IANA registry at
-        // in http://www.iana.org/assignments/character-sets/character-sets.xhtml
+        // http://www.iana.org/assignments/character-sets/character-sets.xhtml
         // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
         /*if (function_exists('mb_list_encodings')) {
 
@@ -201,7 +222,7 @@ class Client
      * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
      * the server returns.
      *
-     * @param integer $in values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
+     * @param integer $level values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
      */
     public function setDebug($level)
     {
@@ -304,7 +325,7 @@ class Client
     }
 
     /**
-     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value ): let cURL decide
+     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
      *
      * @param int $i
      */
@@ -390,7 +411,7 @@ class Client
      */
     public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
     {
-        $this->cookies[$name]['value'] = urlencode($value);
+        $this->cookies[$name]['value'] = rawurlencode($value);
         if ($path || $domain || $port) {
             $this->cookies[$name]['path'] = $path;
             $this->cookies[$name]['domain'] = $domain;
@@ -413,6 +434,15 @@ class Client
         $this->extracurlopts = $options;
     }
 
+    /**
+     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
+     */
+    public function setUseCurl($useCurlMode)
+    {
+        $this->use_curl = $useCurlMode;
+    }
+
+
     /**
      * Set user-agent string that will be used by this client instance in http headers sent to the server.
      *
@@ -448,8 +478,8 @@ class Client
      * @param string $method valid values are 'http', 'http11' and 'https'. If left unspecified, the http protocol
      *                       chosen during creation of the object will be used.
      *
-     *
      * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
+     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
      */
     public function send($req, $timeout = 0, $method = '')
     {
@@ -473,8 +503,13 @@ class Client
         // where req is a Request
         $req->setDebug($this->debug);
 
-        if ($method == 'https') {
-            $r = $this->sendPayloadHTTPS(
+        /// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and
+        ///       needed, such as digest or ntlm auth. Do not attempt to use it for https if not present
+        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
+            ($method == 'https' || $method == 'http11'));
+
+        if ($useCurl) {
+            $r = $this->sendPayloadCURL(
                 $req,
                 $this->server,
                 $this->port,
@@ -491,34 +526,16 @@ class Client
                 $this->proxy_user,
                 $this->proxy_pass,
                 $this->proxy_authtype,
+                // bc
+                $method == 'http11' ? 'http' : $method,
                 $this->keepalive,
                 $this->key,
                 $this->keypass,
                 $this->sslversion
             );
-        } elseif ($method == 'http11') {
-            $r = $this->sendPayloadCURL(
-                $req,
-                $this->server,
-                $this->port,
-                $timeout,
-                $this->username,
-                $this->password,
-                $this->authtype,
-                null,
-                null,
-                null,
-                null,
-                $this->proxy,
-                $this->proxyport,
-                $this->proxy_user,
-                $this->proxy_pass,
-                $this->proxy_authtype,
-                'http',
-                $this->keepalive
-            );
         } else {
-            $r = $this->sendPayloadHTTP10(
+            // plain 'http 1.0': default to using socket
+            $r = $this->sendPayloadSocket(
                 $req,
                 $this->server,
                 $this->port,
@@ -526,12 +543,19 @@ class Client
                 $this->username,
                 $this->password,
                 $this->authtype,
+                $this->cert,
+                $this->certpass,
+                $this->cacert,
+                $this->cacertdir,
                 $this->proxy,
                 $this->proxyport,
                 $this->proxy_user,
                 $this->proxy_pass,
                 $this->proxy_authtype,
-                $method
+                $method,
+                $this->key,
+                $this->keypass,
+                $this->sslversion
             );
         }
 
@@ -539,6 +563,7 @@ class Client
     }
 
     /**
+     * @deprecated
      * @param Request $req
      * @param string $server
      * @param int $port
@@ -557,18 +582,89 @@ class Client
     protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
         $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
         $method='http')
+    {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
+        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
+            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
+    }
+
+    /**
+     * @deprecated
+     * @param Request $req
+     * @param string $server
+     * @param int $port
+     * @param int $timeout
+     * @param string $username
+     * @param string $password
+     * @param int $authType
+     * @param string $cert
+     * @param string $certPass
+     * @param string $caCert
+     * @param string $caCertDir
+     * @param string $proxyHost
+     * @param int $proxyPort
+     * @param string $proxyUsername
+     * @param string $proxyPassword
+     * @param int $proxyAuthType
+     * @param bool $keepAlive
+     * @param string $key
+     * @param string $keyPass
+     * @param int $sslVersion
+     * @return Response
+     */
+    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
+        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
+        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
+        $sslVersion = 0)
+    {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
+        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
+            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
+            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
+    }
+
+    /**
+     * @param Request $req
+     * @param string $server
+     * @param int $port
+     * @param int $timeout
+     * @param string $username
+     * @param string $password
+     * @param int $authType only value supported is 1
+     * @param string $cert
+     * @param string $certPass
+     * @param string $caCert
+     * @param string $caCertDir
+     * @param string $proxyHost
+     * @param int $proxyPort
+     * @param string $proxyUsername
+     * @param string $proxyPassword
+     * @param int $proxyAuthType only value supported is 1
+     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
+     * @param string $key
+     * @param string $keyPass @todo not implemented yet.
+     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
+     * @return Response
+     */
+    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
+        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
+        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', $keyPass = '',
+        $sslVersion = 0)
     {
         if ($port == 0) {
-            $port = ( $method === "https" ) ? 443 : 80;
+            $port = ( $method === 'https' ) ? 443 : 80;
         }
 
         // Only create the payload if it was not created previously
         if (empty($req->payload)) {
-            $req->createPayload($this->request_charset_encoding);
+            $req->serialize($this->request_charset_encoding);
         }
 
         $payload = $req->payload;
         // Deflate request body and set appropriate request headers
+        $encodingHdr = '';
         if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
             if ($this->request_compression == 'gzip') {
                 $a = @gzencode($payload);
@@ -583,8 +679,6 @@ class Client
                     $encodingHdr = "Content-Encoding: deflate\r\n";
                 }
             }
-        } else {
-            $encodingHdr = '';
         }
 
         // thanks to Grant Rauscher <grant7@firstworld.net> for this
@@ -592,7 +686,7 @@ class Client
         if ($username != '') {
             $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
             if ($authType != 1) {
-                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
             }
         }
 
@@ -608,24 +702,22 @@ class Client
             }
             $connectServer = $proxyHost;
             $connectPort = $proxyPort;
-            $transport = "tcp";
+            $transport = 'tcp';
             $uri = 'http://' . $server . ':' . $port . $this->path;
             if ($proxyUsername != '') {
                 if ($proxyAuthType != 1) {
-                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
+                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
                 }
                 $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
             }
         } else {
             $connectServer = $server;
             $connectPort = $port;
-            /// @todo if supporting https, we should support all its current options as well: peer name verification etc...
-            $transport = ( $method === "https" ) ? "tls" : "tcp";
+            $transport = ( $method === 'https' ) ? 'tls' : 'tcp';
             $uri = $this->path;
         }
 
-        // Cookie generation, as per rfc2965 (version 1 cookies) or
-        // netscape's rules (version 0 cookies)
+        // Cookie generation, as per rfc2965 (version 1 cookies) or netscape's rules (version 0 cookies)
         $cookieHeader = '';
         if (count($this->cookies)) {
             $version = '';
@@ -649,8 +741,12 @@ class Client
             $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
         }
 
-        // omit port if 80
-        $port = ($port == 80) ? '' : (':' . $port);
+        // omit port if default
+        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
+            $port =  '';
+        } else {
+            $port = ':' . $port;
+        }
 
         $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
             'User-Agent: ' . $this->user_agent . "\r\n" .
@@ -666,19 +762,53 @@ class Client
             $payload;
 
         if ($this->debug > 1) {
-            Logger::instance()->debugMessage("---SENDING---\n$op\n---END---");
+            $this->getLogger()->debugMessage("---SENDING---\n$op\n---END---");
+        }
+
+        $contextOptions = array();
+        if ($method == 'https') {
+            if ($cert != '') {
+                $contextOptions['ssl']['local_cert'] = $cert;
+                if ($certPass != '') {
+                    $contextOptions['ssl']['passphrase'] = $certPass;
+                }
+            }
+            if ($caCert != '') {
+                $contextOptions['ssl']['cafile'] = $caCert;
+            }
+            if ($caCertDir != '') {
+                $contextOptions['ssl']['capath'] = $caCertDir;
+            }
+            if ($key != '') {
+                $contextOptions['ssl']['local_pk'] = $key;
+            }
+            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
+            $contextOptions['ssl']['verify_peer_name'] = $this->verifypeer;
         }
 
-        if ($timeout > 0) {
-            $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $timeout);
+        $context = stream_context_create($contextOptions);
+
+        if ($timeout <= 0) {
+            $connectTimeout = ini_get('default_socket_timeout');
         } else {
-            $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr);
+            $connectTimeout = $timeout;
         }
+
+        $this->errno = 0;
+        $this->errstr = '';
+
+        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
+            STREAM_CLIENT_CONNECT, $context);
         if ($fp) {
             if ($timeout > 0) {
                 stream_set_timeout($fp, $timeout);
             }
         } else {
+            if ($this->errstr == '') {
+                $err = error_get_last();
+                $this->errstr = $err['message'];
+            }
+
             $this->errstr = 'Connect error: ' . $this->errstr;
             $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
 
@@ -691,12 +821,10 @@ class Client
             $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
 
             return $r;
-        } else {
-            // reset errno and errstr on successful socket connection
-            $this->errstr = '';
         }
-        // G. Giunta 2005/10/24: close socket before parsing.
-        // should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
+
+        // Close socket before parsing.
+        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
         $ipd = '';
         do {
             // shall we check for $data === FALSE?
@@ -704,44 +832,12 @@ class Client
             $ipd .= fread($fp, 32768);
         } while (!feof($fp));
         fclose($fp);
+
         $r = $req->parseResponse($ipd, false, $this->return_type);
 
         return $r;
     }
 
-    /**
-     * @param Request $req
-     * @param string $server
-     * @param int $port
-     * @param int $timeout
-     * @param string $username
-     * @param string $password
-     * @param int $authType
-     * @param string $cert
-     * @param string $certPass
-     * @param string $caCert
-     * @param string $caCertDir
-     * @param string $proxyHost
-     * @param int $proxyPort
-     * @param string $proxyUsername
-     * @param string $proxyPassword
-     * @param int $proxyAuthType
-     * @param bool $keepAlive
-     * @param string $key
-     * @param string $keyPass
-     * @param int $sslVersion
-     * @return Response
-     */
-    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
-        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
-        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
-        $sslVersion = 0)
-    {
-        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
-            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
-            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
-    }
-
     /**
      * Contributed by Justin Miller <justin@voxel.net>
      * Requires curl to be built into PHP
@@ -763,7 +859,7 @@ class Client
      * @param string $proxyUsername
      * @param string $proxyPassword
      * @param int $proxyAuthType
-     * @param string $method
+     * @param string $method 'http' (let curl decide), 'http10', 'http11' or 'https'
      * @param bool $keepAlive
      * @param string $key
      * @param string $keyPass
@@ -789,7 +885,7 @@ class Client
         }
 
         if ($port == 0) {
-            if ($method == 'http') {
+            if (in_array($method, array('http', 'http10', 'http11'))) {
                 $port = 80;
             } else {
                 $port = 443;
@@ -798,7 +894,7 @@ class Client
 
         // Only create the payload if it was not created previously
         if (empty($req->payload)) {
-            $req->createPayload($this->request_charset_encoding);
+            $req->serialize($this->request_charset_encoding);
         }
 
         // Deflate request body and set appropriate request headers
@@ -822,11 +918,16 @@ class Client
         }
 
         if ($this->debug > 1) {
-            Logger::instance()->debugMessage("---SENDING---\n$payload\n---END---");
+            $this->getLogger()->debugMessage("---SENDING---\n$payload\n---END---");
         }
 
         if (!$keepAlive || !$this->xmlrpc_curl_handle) {
-            $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
+            if ($method == 'http11' || $method == 'http10') {
+                $protocol = 'http';
+            } else {
+                $protocol = $method;
+            }
+            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
             if ($keepAlive) {
                 $this->xmlrpc_curl_handle = $curl;
             }
@@ -851,8 +952,7 @@ class Client
         curl_setopt($curl, CURLOPT_HEADER, 1);
 
         // NB: if we set an empty string, CURL will add http header indicating
-        // ALL methods it is supporting. This is possibly a better option than
-        // letting the user tell what curl can / cannot do...
+        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
         if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
             //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
             // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
@@ -873,18 +973,28 @@ class Client
             $headers[] = $encodingHdr;
         }
 
+        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
+        // size exceeds 1025 bytes, apparently)
+        $headers[] = 'Expect:';
+
         curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
         // timeout is borked
         if ($timeout) {
             curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
         }
 
+        if ($method == 'http10') {
+            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+        } elseif ($method == 'http11') {
+            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+        }
+
         if ($username && $password) {
             curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
             if (defined('CURLOPT_HTTPAUTH')) {
                 curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
             } elseif ($authType != 1) {
-                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
             }
         }
 
@@ -914,7 +1024,8 @@ class Client
             if ($keyPass) {
                 curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
             }
-            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
+            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
+            // it matches the hostname used
             curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
             // allow usage of different SSL versions
             curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
@@ -931,14 +1042,13 @@ class Client
                 if (defined('CURLOPT_PROXYAUTH')) {
                     curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
                 } elseif ($proxyAuthType != 1) {
-                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
+                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
                 }
             }
         }
 
         // NB: should we build cookie http headers by hand rather than let CURL do it?
-        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
-        // set to client obj the the user...
+        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj the the user...
         if (count($this->cookies)) {
             $cookieHeader = '';
             foreach ($this->cookies as $name => $cookie) {
@@ -961,8 +1071,8 @@ class Client
                 }
                 $message .= $name . ': ' . $val . "\n";
             }
-            $message .= "---END---";
-            Logger::instance()->debugMessage($message);
+            $message .= '---END---';
+            $this->getLogger()->debugMessage($message);
         }
 
         if (!$result) {
@@ -1168,10 +1278,12 @@ class Client
                         break;
                     case 'struct':
                         $code = $val['faultCode'];
+                        /** @var Value $code */
                         if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
                             return false;
                         }
                         $str = $val['faultString'];
+                        /** @var Value $str */
                         if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
                             return false;
                         }
index dfec15d..7ad2adb 100644 (file)
@@ -2,32 +2,63 @@
 
 namespace PhpXmlRpc;
 
+use PhpXmlRpc\Helper\Logger;
 use PhpXmlRpc\Helper\XMLParser;
 
 /**
  * A helper class to easily convert between Value objects and php native values
+ * @todo implement an interface
+ * @todo add class constants for the options values
  */
 class Encoder
 {
+    protected static $logger;
+    protected static $parser;
+
+    public function getLogger()
+    {
+        if (self::$logger === null) {
+            self::$logger = Logger::instance();
+        }
+        return self::$logger;
+    }
+
+    public static function setLogger($logger)
+    {
+        self::$logger = $logger;
+    }
+
+    public function getParser()
+    {
+        if (self::$parser === null) {
+            self::$parser = new XMLParser();
+        }
+        return self::$parser;
+    }
+
+    public static function setParser($parser)
+    {
+        self::$parser = $parser;
+    }
+
     /**
      * Takes an xmlrpc value in object format and translates it into native PHP types.
      *
      * Works with xmlrpc requests objects as input, too.
      *
-     * Given proper options parameter, can rebuild generic php object instances
-     * (provided those have been encoded to xmlrpc format using a corresponding
-     * option in php_xmlrpc_encode())
+     * Given proper options parameter, can rebuild generic php object instances (provided those have been encoded to
+     * xmlrpc format using a corresponding option in php_xmlrpc_encode())
      * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
-     * This means that the remote communication end can decide which php code will
-     * get executed on your server, leaving the door possibly open to 'php-injection'
-     * style of attacks (provided you have some classes defined on your server that
-     * might wreak havoc if instances are built outside an appropriate context).
-     * Make sure you trust the remote server/client before eanbling this!
+     * This means that the remote communication end can decide which php code will get executed on your server, leaving
+     * the door possibly open to 'php-injection' style of attacks (provided you have some classes defined on your server
+     * that might wreak havoc if instances are built outside an appropriate context).
+     * Make sure you trust the remote server/client before enabling this!
      *
      * @author Dan Libby (dan@libby.com)
      *
      * @param Value|Request $xmlrpcVal
-     * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects (standard is
+     * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php
+     *                       objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects
      *
      * @return mixed
      */
@@ -36,28 +67,38 @@ class Encoder
         switch ($xmlrpcVal->kindOf()) {
             case 'scalar':
                 if (in_array('extension_api', $options)) {
-                    reset($xmlrpcVal->me);
-                    list($typ, $val) = each($xmlrpcVal->me);
+                    $val = reset($xmlrpcVal->me);
+                    $typ = key($xmlrpcVal->me);
                     switch ($typ) {
                         case 'dateTime.iso8601':
-                            $xmlrpcVal->scalar = $val;
-                            $xmlrpcVal->type = 'datetime';
-                            $xmlrpcVal->timestamp = \PhpXmlRpc\Helper\Date::iso8601Decode($val);
-
-                            return $xmlrpcVal;
+                            $xmlrpcVal = array(
+                                'xmlrpc_type' => 'datetime',
+                                'scalar' => $val,
+                                'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($val)
+                            );
+                            return (object)$xmlrpcVal;
                         case 'base64':
-                            $xmlrpcVal->scalar = $val;
-                            $xmlrpcVal->type = $typ;
-
-                            return $xmlrpcVal;
+                            $xmlrpcVal = array(
+                                'xmlrpc_type' => 'base64',
+                                'scalar' => $val
+                            );
+                            return (object)$xmlrpcVal;
+                        case 'string':
+                            if (isset($options['extension_api_encoding'])) {
+                                $dval = @iconv('UTF-8', $options['extension_api_encoding'], $val);
+                                if ($dval !== false) {
+                                    return $dval;
+                                }
+                            }
+                            //return $val;
+                            // break through voluntarily
                         default:
-                            return $xmlrpcVal->scalarval();
+                            return $val;
                     }
                 }
                 if (in_array('dates_as_objects', $options) && $xmlrpcVal->scalartyp() == 'dateTime.iso8601') {
-                    // we return a Datetime object instead of a string
-                    // since now the constructor of xmlrpc value accepts safely strings, ints and datetimes,
-                    // we cater to all 3 cases here
+                    // we return a Datetime object instead of a string since now the constructor of xmlrpc value accepts
+                    // safely strings, ints and datetimes, we cater to all 3 cases here
                     $out = $xmlrpcVal->scalarval();
                     if (is_string($out)) {
                         $out = strtotime($out);
@@ -71,20 +112,20 @@ class Encoder
                         return $out;
                     }
                 }
-
                 return $xmlrpcVal->scalarval();
+
             case 'array':
                 $arr = array();
                 foreach($xmlrpcVal as $value) {
                     $arr[] = $this->decode($value, $options);
                 }
-
                 return $arr;
+
             case 'struct':
                 // If user said so, try to rebuild php objects for specific struct vals.
                 /// @todo should we raise a warning for class not found?
-                // shall we check for proper subclass of xmlrpc value instead of
-                // presence of _php_class to detect what we can do?
+                // shall we check for proper subclass of xmlrpc value instead of presence of _php_class to detect
+                // what we can do?
                 if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != ''
                     && class_exists($xmlrpcVal->_php_class)
                 ) {
@@ -92,24 +133,24 @@ class Encoder
                     foreach ($xmlrpcVal as $key => $value) {
                         $obj->$key = $this->decode($value, $options);
                     }
-
                     return $obj;
                 } else {
                     $arr = array();
                     foreach ($xmlrpcVal as $key => $value) {
                         $arr[$key] = $this->decode($value, $options);
                     }
-
                     return $arr;
                 }
+
             case 'msg':
                 $paramCount = $xmlrpcVal->getNumParams();
                 $arr = array();
                 for ($i = 0; $i < $paramCount; $i++) {
                     $arr[] = $this->decode($xmlrpcVal->getParam($i), $options);
                 }
-
                 return $arr;
+
+            /// @todo throw on unsupported type
         }
     }
 
@@ -120,22 +161,22 @@ class Encoder
      * Feature creep -- could support more types via optional type argument
      * (string => datetime support has been added, ??? => base64 not yet)
      *
-     * If given a proper options parameter, php object instances will be encoded
-     * into 'special' xmlrpc values, that can later be decoded into php objects
-     * by calling php_xmlrpc_decode() with a corresponding option
+     * If given a proper options parameter, php object instances will be encoded into 'special' xmlrpc values, that can
+     * later be decoded into php objects by calling php_xmlrpc_decode() with a corresponding option
      *
      * @author Dan Libby (dan@libby.com)
      *
      * @param mixed $phpVal the value to be converted into an xmlrpc value object
      * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
      *
-     * @return \PhpXmlrpc\Value
+     * @return Value
      */
     public function encode($phpVal, $options = array())
     {
         $type = gettype($phpVal);
         switch ($type) {
             case 'string':
+                /// @todo should we be stricter in the accepted dates (ie. reject more of invalid days & times)?
                 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $phpVal)) {
                     $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime);
                 } else {
@@ -148,15 +189,13 @@ class Encoder
             case 'double':
                 $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble);
                 break;
-            // <G_Giunta_2001-02-29>
             // Add support for encoding/decoding of booleans, since they are supported in PHP
             case 'boolean':
                 $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean);
                 break;
-            // </G_Giunta_2001-02-29>
             case 'array':
-                // PHP arrays can be encoded to either xmlrpc structs or arrays,
-                // depending on wheter they are hashes or plain 0..n integer indexed
+                // PHP arrays can be encoded to either xmlrpc structs or arrays, depending on whether they are hashes
+                // or plain 0..n integer indexed
                 // A shorter one-liner would be
                 // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1));
                 // but execution time skyrockets!
@@ -180,11 +219,27 @@ class Encoder
                 if (is_a($phpVal, 'PhpXmlRpc\Value')) {
                     $xmlrpcVal = $phpVal;
                 } elseif (is_a($phpVal, 'DateTimeInterface')) {
-                    $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcStruct);
+                    $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcDateTime);
+                } elseif (in_array('extension_api', $options) && $phpVal instanceof \stdClass && isset($phpVal->xmlrpc_type)) {
+                    // Handle the 'pre-converted' base64 and datetime values
+                    if (isset($phpVal->scalar)) {
+                        switch ($phpVal->xmlrpc_type) {
+                            case 'base64':
+                                $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcBase64);
+                                break;
+                            case 'datetime':
+                                $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcDateTime);
+                                break;
+                            default:
+                                $xmlrpcVal = new Value();
+                        }
+                    } else {
+                        $xmlrpcVal = new Value();
+                    }
+
                 } else {
                     $arr = array();
-                    reset($phpVal);
-                    while (list($k, $v) = each($phpVal)) {
+                    foreach($phpVal as $k => $v) {
                         $arr[$k] = $this->encode($v, $options);
                     }
                     $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct);
@@ -210,11 +265,11 @@ class Encoder
                 } else {
                     $xmlrpcVal = new Value();
                 }
+                break;
             // catch "user function", "unknown type"
             default:
                 // giancarlo pinerolo <ping@alt.it>
-                // it has to return
-                // an empty object in case, not a boolean.
+                // it has to return an empty object in case, not a boolean.
                 $xmlrpcVal = new Value();
                 break;
         }
@@ -226,10 +281,13 @@ class Encoder
      * Convert the xml representation of a method response, method request or single
      * xmlrpc value into the appropriate object (a.k.a. deserialize).
      *
+     * @todo is this a good name/class for this method? It does something quite different from 'decode' after all
+     *       (returning objects vs returns plain php values)... In fact it belongs rather to a Parser class
+     *
      * @param string $xmlVal
      * @param array $options
      *
-     * @return mixed false on error, or an instance of either Value, Request or Response
+     * @return Value|Request|Response|false false on error, or an instance of either Value, Request or Response
      */
     public function decodeXml($xmlVal, $options = array())
     {
@@ -237,10 +295,12 @@ class Encoder
         $valEncoding = XMLParser::guessEncoding('', $xmlVal);
         if ($valEncoding != '') {
 
-            // Since parsing will fail if charset is not specified in the xml prologue,
-            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
-            // The following code might be better for mb_string enabled installs, but
-            // makes the lib about 200% slower...
+            // Since parsing will fail if
+            // - charset is not specified in the xml prologue,
+            // - the encoding is not UTF8 and
+            // - there are non-ascii chars in the text,
+            // we try to work round that...
+            // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower...
             //if (!is_valid_charset($valEncoding, array('UTF-8'))
             if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) {
                 if ($valEncoding == 'ISO-8859-1') {
@@ -249,69 +309,74 @@ class Encoder
                     if (extension_loaded('mbstring')) {
                         $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding);
                     } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding);
+                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding);
                     }
                 }
             }
         }
 
-        $parser = xml_parser_create();
-        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
-        // What if internal encoding is not in one of the 3 allowed?
-        // we use the broadest one, ie. utf8!
+        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8!
         if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
-            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
+            /// @todo emit a warning
+            $parserOptions = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
         } else {
-            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding);
+            $parserOptions = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
         }
 
-        $xmlRpcParser = new XMLParser();
-        xml_set_object($parser, $xmlRpcParser);
-
-        xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
-        xml_set_character_data_handler($parser, 'xmlrpc_cd');
-        xml_set_default_handler($parser, 'xmlrpc_dh');
-        if (!xml_parse($parser, $xmlVal, 1)) {
-            $errstr = sprintf('XML error: %s at line %d, column %d',
-                xml_error_string(xml_get_error_code($parser)),
-                xml_get_current_line_number($parser), xml_get_current_column_number($parser));
-            error_log($errstr);
-            xml_parser_free($parser);
+        $xmlRpcParser = $this->getParser();
+        $xmlRpcParser->parse(
+            $xmlVal,
+            XMLParser::RETURN_XMLRPCVALS,
+            XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE | XMLParser::ACCEPT_FAULT,
+            $parserOptions
+        );
 
-            return false;
-        }
-        xml_parser_free($parser);
         if ($xmlRpcParser->_xh['isf'] > 1) {
             // test that $xmlrpc->_xh['value'] is an obj, too???
 
-            error_log($xmlRpcParser->_xh['isf_reason']);
+            $this->getLogger()->errorLog($xmlRpcParser->_xh['isf_reason']);
 
             return false;
         }
+
         switch ($xmlRpcParser->_xh['rt']) {
             case 'methodresponse':
-                $v = &$xmlRpcParser->_xh['value'];
+                $v = $xmlRpcParser->_xh['value'];
                 if ($xmlRpcParser->_xh['isf'] == 1) {
+                    /** @var Value $vc */
                     $vc = $v['faultCode'];
+                    /** @var Value $vs */
                     $vs = $v['faultString'];
                     $r = new Response(0, $vc->scalarval(), $vs->scalarval());
                 } else {
                     $r = new Response($v);
                 }
-
                 return $r;
+
             case 'methodcall':
                 $req = new Request($xmlRpcParser->_xh['method']);
                 for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) {
                     $req->addParam($xmlRpcParser->_xh['params'][$i]);
                 }
-
                 return $req;
+
             case 'value':
                 return $xmlRpcParser->_xh['value'];
+
+            case 'fault':
+                // EPI api emulation
+                $v = $xmlRpcParser->_xh['value'];
+                // use a known error code
+                /** @var Value $vc */
+                $vc = isset($v['faultCode']) ? $v['faultCode']->scalarval() : PhpXmlRpc::$xmlrpcerr['invalid_return'];
+                /** @var Value $vs */
+                $vs = isset($v['faultString']) ? $v['faultString']->scalarval() : '';
+                if (!is_int($vc) || $vc == 0) {
+                    $vc = PhpXmlRpc::$xmlrpcerr['invalid_return'];
+                }
+                return new Response(0, $vc, $vs);
             default:
                 return false;
         }
     }
-
 }
diff --git a/php/phpxmlrpc/src/Exception/HttpException.php b/php/phpxmlrpc/src/Exception/HttpException.php
new file mode 100644 (file)
index 0000000..5b9322e
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+class HttpException extends PhpXmlrpcException
+{
+    protected $statusCode;
+
+    public function __construct($message = "", $code = 0, $previous = null, $statusCode = null)
+    {
+        parent::__construct($message, $code, $previous);
+        $this->statusCode = $statusCode;
+    }
+
+    public function statusCode()
+    {
+        return $this->statusCode;
+    }
+}
diff --git a/php/phpxmlrpc/src/Exception/PhpXmlrpcException.php b/php/phpxmlrpc/src/Exception/PhpXmlrpcException.php
new file mode 100644 (file)
index 0000000..e143f4e
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+namespace PhpXmlRpc\Exception;
+
+class PhpXmlrpcException extends \Exception
+{
+}
index 4f1103b..5fa5422 100644 (file)
@@ -4,27 +4,15 @@ namespace PhpXmlRpc\Helper;
 
 use PhpXmlRpc\PhpXmlRpc;
 
+/**
+ * @todo implement an interface
+ */
 class Charset
 {
     // tables used for transcoding different charsets into us-ascii xml
     protected $xml_iso88591_Entities = array("in" => array(), "out" => array());
-    protected $xml_iso88591_utf8 = array("in" => array(), "out" => array());
 
-    /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?
-    /// These will NOT be present in true ISO-8859-1, but will save the unwary
-    /// windows user from sending junk (though no luck when receiving them...)
-    /*
-    protected $xml_cp1252_Entities = array('in' => array(), out' => array(
-        '&#x20AC;', '?',        '&#x201A;', '&#x0192;',
-        '&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
-        '&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
-        '&#x0152;', '?',        '&#x017D;', '?',
-        '?',        '&#x2018;', '&#x2019;', '&#x201C;',
-        '&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
-        '&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
-        '&#x0153;', '?',        '&#x017E;', '&#x0178;'
-    ));
-    */
+    //protected $xml_cp1252_Entities = array('in' => array(), out' => array());
 
     protected $charset_supersets = array(
         'US-ASCII' => array('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
@@ -34,52 +22,112 @@ class Charset
             'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN',),
     );
 
+    /** @var Charset $instance */
     protected static $instance = null;
 
     /**
      * This class is singleton for performance reasons.
+     * @todo should we just make $xml_iso88591_Entities a static variable instead ?
      *
      * @return Charset
      */
     public static function instance()
     {
         if (self::$instance === null) {
-            self::$instance = new self();
+            self::$instance = new static();
         }
 
         return self::$instance;
     }
 
-    private function __construct()
+    /**
+     * Force usage as singleton
+     */
+    protected function __construct()
     {
-        for ($i = 0; $i < 32; $i++) {
-            $this->xml_iso88591_Entities["in"][] = chr($i);
-            $this->xml_iso88591_Entities["out"][] = "&#{$i};";
-        }
+    }
 
-        for ($i = 160; $i < 256; $i++) {
-            $this->xml_iso88591_Entities["in"][] = chr($i);
-            $this->xml_iso88591_Entities["out"][] = "&#{$i};";
-        }
+    /**
+     * @param string $tableName
+     * @throws \Exception for unsupported $tableName
+     * @todo add support for cp1252 as well as latin-2 .. latin-10
+     *       Optimization creep: instead of building all those tables on load, keep them ready-made php files
+     *       which are not even included until needed
+     * @todo should we add to the latin-1 table the characters from cp_1252 range, i.e. 128 to 159 ?
+     *       Those will NOT be present in true ISO-8859-1, but will save the unwary windows user from sending junk
+     *       (though no luck when receiving them...)
+     *       Note also that, apparently, while 'ISO/IEC 8859-1' has no characters defined for bytes 128 to 159,
+     *       IANA ISO-8859-1 does have well-defined 'C1' control codes for those - wikipedia's page on latin-1 says:
+     *       "ISO-8859-1 is the IANA preferred name for this standard when supplemented with the C0 and C1 control codes from ISO/IEC 6429."
+     *       Check what mbstring/iconv do by default with those?
+     */
+    protected function buildConversionTable($tableName)
+    {
+        switch($tableName) {
+            case 'xml_iso88591_Entities':
+                if (count($this->xml_iso88591_Entities['in'])) {
+                    return;
+                }
+                for ($i = 0; $i < 32; $i++) {
+                    $this->xml_iso88591_Entities["in"][] = chr($i);
+                    $this->xml_iso88591_Entities["out"][] = "&#{$i};";
+                }
 
-        /*for ($i = 128; $i < 160; $i++)
-        {
-            $this->xml_cp1252_Entities['in'][] = chr($i);
-        }*/
+                /// @todo to be 'print safe', should we encode as well character 127 (DEL) ?
+
+                for ($i = 160; $i < 256; $i++) {
+                    $this->xml_iso88591_Entities["in"][] = chr($i);
+                    $this->xml_iso88591_Entities["out"][] = "&#{$i};";
+                }
+                break;
+
+            /*case 'xml_cp1252_Entities':
+                if (count($this->xml_cp1252_Entities['in'])) {
+                    return;
+                }
+                for ($i = 128; $i < 160; $i++)
+                {
+                    $this->xml_cp1252_Entities['in'][] = chr($i);
+                }
+                $this->xml_cp1252_Entities['out'] = array(
+                    '&#x20AC;', '?',        '&#x201A;', '&#x0192;',
+                    '&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
+                    '&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
+                    '&#x0152;', '?',        '&#x017D;', '?',
+                    '?',        '&#x2018;', '&#x2019;', '&#x201C;',
+                    '&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
+                    '&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
+                    '&#x0153;', '?',        '&#x017E;', '&#x0178;'
+                );
+                $this->buildConversionTable('xml_iso88591_Entities');
+                break;*/
+
+            default:
+                throw new \Exception('Unsupported table: ' . $tableName);
+        }
     }
 
     /**
      * Convert a string to the correct XML representation in a target charset.
+     * This involves:
+     * - character transformation for all characters which have a different representation in source and dest charsets
+     * - using 'charset entity' representation for all characters which are outside of the target charset
      *
      * To help correct communication of non-ascii chars inside strings, regardless of the charset used when sending
      * requests, parsing them, sending responses and parsing responses, an option is to convert all non-ascii chars
      * present in the message into their equivalent 'charset entity'. Charset entities enumerated this way are
      * independent of the charset encoding used to transmit them, and all XML parsers are bound to understand them.
-     * Note that in the std case we are not sending a charset encoding mime type along with http headers, so we are
-     * bound by RFC 3023 to emit strict us-ascii.
+     *
+     * Note that when not sending a charset encoding mime type along with http headers, we are bound by RFC 3023 to emit
+     * strict us-ascii for 'text/xml' payloads (but we should review RFC 7303, which seems to have changed the rules...)
      *
      * @todo do a bit of basic benchmarking (strtr vs. str_replace)
-     * @todo make usage of iconv() or recode_string() or mb_string() where available
+     * @todo make usage of iconv() or mb_string() where available
+     * @todo support aliases for charset names, eg ASCII, LATIN1, ISO-88591 (see f.e. polyfill-iconv for a list),
+     *       but then take those into account as well in other methods, ie.isValidCharset)
+     * @todo when converting to ASCII, allow to choose whether to escape the range 0-31,127 (non-print chars) or not
+     * @todo allow picking different strategies to deal w. invalid chars? eg. source in latin-1 and chars 128-159
+     * @todo add support for escaping using CDATA sections? (add cdata start and end tokens, replace only ']]>' with ']]]]><![CDATA[>')
      *
      * @param string $data
      * @param string $srcEncoding
@@ -94,30 +142,23 @@ class Charset
             $srcEncoding = PhpXmlRpc::$xmlrpc_internalencoding;
         }
 
-        $conversion = strtoupper($srcEncoding . '_' . $destEncoding);
-        switch ($conversion) {
-            case 'ISO-8859-1_':
-            case 'ISO-8859-1_US-ASCII':
-                $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
-                $escapedData = str_replace($this->xml_iso88591_Entities['in'], $this->xml_iso88591_Entities['out'], $escapedData);
-                break;
+        if ($destEncoding == '') {
+            $destEncoding = 'US-ASCII';
+        }
 
-            case 'ISO-8859-1_UTF-8':
-                $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
-                $escapedData = utf8_encode($escapedData);
-                break;
+        $conversion = strtoupper($srcEncoding . '_' . $destEncoding);
 
+        // list ordered with (expected) most common scenarios first
+        switch ($conversion) {
+            case 'UTF-8_UTF-8':
             case 'ISO-8859-1_ISO-8859-1':
-            case 'US-ASCII_US-ASCII':
             case 'US-ASCII_UTF-8':
-            case 'US-ASCII_':
+            case 'US-ASCII_US-ASCII':
             case 'US-ASCII_ISO-8859-1':
-            case 'UTF-8_UTF-8':
             //case 'CP1252_CP1252':
                 $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
                 break;
 
-            case 'UTF-8_':
             case 'UTF-8_US-ASCII':
             case 'UTF-8_ISO-8859-1':
                 // NB: this will choke on invalid UTF-8, going most likely beyond EOF
@@ -128,9 +169,17 @@ class Charset
                 for ($nn = 0; $nn < $ns; $nn++) {
                     $ch = $data[$nn];
                     $ii = ord($ch);
-                    // 7 bits: 0bbbbbbb (127)
-                    if ($ii < 128) {
+                    // 7 bits in 1 byte: 0bbbbbbb (127)
+                    if ($ii < 32) {
+                        if ($conversion == 'UTF-8_US-ASCII') {
+                            $escapedData .= sprintf('&#%d;', $ii);
+                        } else {
+                            $escapedData .= $ch;
+                        }
+                    }
+                    else if ($ii < 128) {
                         /// @todo shall we replace this with a (supposedly) faster str_replace?
+                        /// @todo to be 'print safe', should we encode as well character 127 (DEL) ?
                         switch ($ii) {
                             case 34:
                                 $escapedData .= '&quot;';
@@ -150,62 +199,67 @@ class Charset
                             default:
                                 $escapedData .= $ch;
                         } // switch
-                    } // 11 bits: 110bbbbb 10bbbbbb (2047)
+                    } // 11 bits in 2 bytes: 110bbbbb 10bbbbbb (2047)
                     elseif ($ii >> 5 == 6) {
                         $b1 = ($ii & 31);
-                        $ii = ord($data[$nn + 1]);
-                        $b2 = ($ii & 63);
+                        $b2 = (ord($data[$nn + 1]) & 63);
                         $ii = ($b1 * 64) + $b2;
-                        $ent = sprintf('&#%d;', $ii);
-                        $escapedData .= $ent;
+                        $escapedData .= sprintf('&#%d;', $ii);
                         $nn += 1;
-                    } // 16 bits: 1110bbbb 10bbbbbb 10bbbbbb
+                    } // 16 bits in 3 bytes: 1110bbbb 10bbbbbb 10bbbbbb
                     elseif ($ii >> 4 == 14) {
                         $b1 = ($ii & 15);
-                        $ii = ord($data[$nn + 1]);
-                        $b2 = ($ii & 63);
-                        $ii = ord($data[$nn + 2]);
-                        $b3 = ($ii & 63);
+                        $b2 = (ord($data[$nn + 1]) & 63);
+                        $b3 = (ord($data[$nn + 2]) & 63);
                         $ii = ((($b1 * 64) + $b2) * 64) + $b3;
-                        $ent = sprintf('&#%d;', $ii);
-                        $escapedData .= $ent;
+                        $escapedData .= sprintf('&#%d;', $ii);
                         $nn += 2;
-                    } // 21 bits: 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
+                    } // 21 bits in 4 bytes: 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
                     elseif ($ii >> 3 == 30) {
                         $b1 = ($ii & 7);
-                        $ii = ord($data[$nn + 1]);
-                        $b2 = ($ii & 63);
-                        $ii = ord($data[$nn + 2]);
-                        $b3 = ($ii & 63);
-                        $ii = ord($data[$nn + 3]);
-                        $b4 = ($ii & 63);
+                        $b2 = (ord($data[$nn + 1]) & 63);
+                        $b3 = (ord($data[$nn + 2]) & 63);
+                        $b4 = (ord($data[$nn + 3]) & 63);
                         $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
-                        $ent = sprintf('&#%d;', $ii);
-                        $escapedData .= $ent;
+                        $escapedData .= sprintf('&#%d;', $ii);
                         $nn += 3;
                     }
                 }
 
                 // when converting to latin-1, do not be so eager with using entities for characters 160-255
                 if ($conversion == 'UTF-8_ISO-8859-1') {
+                    $this->buildConversionTable('xml_iso88591_Entities');
                     $escapedData = str_replace(array_slice($this->xml_iso88591_Entities['out'], 32), array_slice($this->xml_iso88591_Entities['in'], 32), $escapedData);
                 }
                 break;
 
+            case 'ISO-8859-1_UTF-8':
+                $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
+                $escapedData = utf8_encode($escapedData);
+                break;
+
+            case 'ISO-8859-1_US-ASCII':
+                $this->buildConversionTable('xml_iso88591_Entities');
+                $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
+                $escapedData = str_replace($this->xml_iso88591_Entities['in'], $this->xml_iso88591_Entities['out'], $escapedData);
+                break;
+
             /*
-            case 'CP1252_':
             case 'CP1252_US-ASCII':
+                $this->buildConversionTable('xml_cp1252_Entities');
                 $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
                 $escapedData = str_replace($this->xml_iso88591_Entities']['in'], $this->xml_iso88591_Entities['out'], $escapedData);
                 $escapedData = str_replace($this->xml_cp1252_Entities['in'], $this->xml_cp1252_Entities['out'], $escapedData);
                 break;
             case 'CP1252_UTF-8':
+                $this->buildConversionTable('xml_cp1252_Entities');
                 $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
-                /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
+                /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all alone will NOT convert them)
                 $escapedData = str_replace($this->xml_cp1252_Entities['in'], $this->xml_cp1252_Entities['out'], $escapedData);
                 $escapedData = utf8_encode($escapedData);
                 break;
             case 'CP1252_ISO-8859-1':
+                $this->buildConversionTable('xml_cp1252_Entities');
                 $escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
                 // we might as well replace all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...
                 $escapedData = str_replace($this->xml_cp1252_Entities['in'], $this->xml_cp1252_Entities['out'], $escapedData);
@@ -214,15 +268,15 @@ class Charset
 
             default:
                 $escapedData = '';
-                error_log('XML-RPC: ' . __METHOD__ . ": Converting from $srcEncoding to $destEncoding: not supported...");
+                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ": Converting from $srcEncoding to $destEncoding: not supported...");
         }
 
         return $escapedData;
     }
 
     /**
-     * Checks if a given charset encoding is present in a list of encodings or
-     * if it is a valid subset of any encoding in the list.
+     * Checks if a given charset encoding is present in a list of encodings or if it is a valid subset of any encoding
+     * in the list.
      *
      * @param string $encoding charset to be tested
      * @param string|array $validList comma separated list of valid charsets (or array of charsets)
@@ -261,6 +315,8 @@ class Charset
      */
     public function getEntities($charset)
     {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
         switch ($charset)
         {
             case 'iso88591':
@@ -269,5 +325,4 @@ class Charset
                 throw new \Exception('Unsupported charset: ' . $charset);
         }
     }
-
 }
index f97f52c..bc60cc4 100644 (file)
@@ -7,15 +7,13 @@ class Date
     /**
      * Given a timestamp, return the corresponding ISO8601 encoded string.
      *
-     * Really, timezones ought to be supported
-     * but the XML-RPC spec says:
+     * Really, timezones ought to be supported but the XML-RPC spec says:
      *
-     * "Don't assume a timezone. It should be specified by the server in its
-     * documentation what assumptions it makes about timezones."
+     * "Don't assume a timezone. It should be specified by the server in its documentation what assumptions it makes
+     *  about timezones."
      *
-     * These routines always assume localtime unless
-     * $utc is set to 1, in which case UTC is assumed
-     * and an adjustment for locale is made when encoding
+     * These routines always assume localtime unless $utc is set to 1, in which case UTC is assumed and an adjustment
+     * for locale is made when encoding
      *
      * @param int $timet (timestamp)
      * @param int $utc (0 or 1)
@@ -28,8 +26,7 @@ class Date
             $t = strftime("%Y%m%dT%H:%M:%S", $timet);
         } else {
             if (function_exists('gmstrftime')) {
-                // gmstrftime doesn't exist in some versions
-                // of PHP
+                // gmstrftime doesn't exist in some versions of PHP
                 $t = gmstrftime("%Y%m%dT%H:%M:%S", $timet);
             } else {
                 $t = strftime("%Y%m%dT%H:%M:%S", $timet - date('Z'));
@@ -50,7 +47,7 @@ class Date
     public static function iso8601Decode($idate, $utc = 0)
     {
         $t = 0;
-        if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) {
+        if (preg_match('/([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9]):([0-5][0-9]):([0-5][0-9])/', $idate, $regs)) {
             if ($utc) {
                 $t = gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
             } else {
index 2faf583..77d4a9d 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace PhpXmlRpc\Helper;
 
+use PhpXmlRpc\Exception\HttpException;
 use PhpXmlRpc\PhpXmlRpc;
 
 class Http
@@ -13,6 +14,7 @@ class Http
      * @param string $buffer the string to be decoded
      *
      * @return string
+     * @internal this function will become protected in the future
      */
     public static function decodeChunked($buffer)
     {
@@ -62,17 +64,17 @@ class Http
     /**
      * Parses HTTP an http response headers and separates them from the body.
      *
-     * @param string $data the http response,headers and body. It will be stripped of headers
+     * @param string $data the http response, headers and body. It will be stripped of headers
      * @param bool $headersProcessed when true, we assume that response inflating and dechunking has been already carried out
      *
-     * @return array with keys 'headers' and 'cookies'
-     * @throws \Exception
+     * @return array with keys 'headers', 'cookies', 'raw_data' and 'status_code'
+     * @throws HttpException
      */
     public function parseResponseHeaders(&$data, $headersProcessed = false, $debug=0)
     {
-        $httpResponse = array('raw_data' => $data, 'headers'=> array(), 'cookies' => array());
+        $httpResponse = array('raw_data' => $data, 'headers'=> array(), 'cookies' => array(), 'status_code' => null);
 
-        // Support "web-proxy-tunelling" connections for https through proxies
+        // Support "web-proxy-tunnelling" connections for https through proxies
         if (preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) {
             // Look for CR/LF or simple LF as line separator,
             // (even though it is not valid http)
@@ -93,8 +95,8 @@ class Http
                 // maybe we could take them into account, too?
                 $data = substr($data, $bd);
             } else {
-                error_log('XML-RPC: ' . __METHOD__ . ': HTTPS via proxy error, tunnel connection possibly failed');
-                throw new \Exception(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (HTTPS via proxy error, tunnel connection possibly failed)', PhpXmlRpc::$xmlrpcerr['http_error']);
+                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': HTTPS via proxy error, tunnel connection possibly failed');
+                throw new HttpException(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (HTTPS via proxy error, tunnel connection possibly failed)', PhpXmlRpc::$xmlrpcerr['http_error']);
             }
         }
 
@@ -104,16 +106,29 @@ class Http
             // server sent a Continue header without any (valid) content following...
             // give the client a chance to know it
             if (!$pos && !is_int($pos)) {
-                // works fine in php 3, 4 and 5
+                /// @todo this construct works fine in php 3, 4 and 5 - 8; would it not be enough to have !== false now ?
 
                 break;
             }
             $data = substr($data, $pos);
         }
-        if (!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) {
+
+        // When using Curl to query servers using Digest Auth, we get back a double set of http headers.
+        // We strip out the 1st...
+        if ($headersProcessed && preg_match('/^HTTP\/[0-9](?:\.[0-9])? 401 /', $data)) {
+            if (preg_match('/(\r?\n){2}HTTP\/[0-9](?:\.[0-9])? 200 /', $data)) {
+                $data = preg_replace('/^HTTP\/[0-9](?:\.[0-9])? 401 .+?(?:\r?\n){2}(HTTP\/[0-9.]+ 200 )/s', '$1', $data, 1);
+            }
+        }
+
+        if (preg_match('/^HTTP\/[0-9](?:\.[0-9])? ([0-9]{3}) /', $data, $matches)) {
+            $httpResponse['status_code'] = $matches[1];
+        }
+
+        if ($httpResponse['status_code'] !== '200') {
             $errstr = substr($data, 0, strpos($data, "\n") - 1);
-            error_log('XML-RPC: ' . __METHOD__ . ': HTTP error, got response: ' . $errstr);
-            throw new \Exception(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')', PhpXmlRpc::$xmlrpcerr['http_error']);
+            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': HTTP error, got response: ' . $errstr);
+            throw new HttpException(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')', PhpXmlRpc::$xmlrpcerr['http_error'], null, $httpResponse['status_code'] );
         }
 
         // be tolerant to usage of \n instead of \r\n to separate headers and data
@@ -131,17 +146,20 @@ class Http
                 $bd = 0;
             }
         }
+
         // be tolerant to line endings, and extra empty lines
         $ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
-        while (list(, $line) = @each($ar)) {
+
+        foreach($ar as $line) {
             // take care of multi-line headers and cookies
             $arr = explode(':', $line, 2);
             if (count($arr) > 1) {
                 $headerName = strtolower(trim($arr[0]));
                 /// @todo some other headers (the ones that allow a CSV list of values)
-                /// do allow many values to be passed using multiple header lines.
-                /// We should add content to $xmlrpc->_xh['headers'][$headerName]
-                /// instead of replacing it for those...
+                ///       do allow many values to be passed using multiple header lines.
+                ///       We should add content to $xmlrpc->_xh['headers'][$headerName]
+                ///       instead of replacing it for those...
+                /// @todo should we drop support for rfc2965 (set-cookie2) cookies? It has been obsoleted since 2011
                 if ($headerName == 'set-cookie' || $headerName == 'set-cookie2') {
                     if ($headerName == 'set-cookie2') {
                         // version 2 cookies:
@@ -165,7 +183,7 @@ class Http
                         foreach ($cookie as $pos => $val) {
                             $val = explode('=', $val, 2);
                             $tag = trim($val[0]);
-                            $val = trim(@$val[1]);
+                            $val = isset($val[1]) ? trim($val[1]) : '';
                             /// @todo with version 1 cookies, we should strip leading and trailing " chars
                             if ($pos == 0) {
                                 $cookiename = $tag;
@@ -203,11 +221,12 @@ class Http
         // if CURL was used for the call, http headers have been processed,
         // and dechunking + reinflating have been carried out
         if (!$headersProcessed) {
+
             // Decode chunked encoding sent by http 1.1 servers
             if (isset($httpResponse['headers']['transfer-encoding']) && $httpResponse['headers']['transfer-encoding'] == 'chunked') {
-                if (!$data = Http::decodeChunked($data)) {
-                    error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to rebuild the chunked data received from server');
-                    throw new \Exception(PhpXmlRpc::$xmlrpcstr['dechunk_fail'], PhpXmlRpc::$xmlrpcerr['dechunk_fail']);
+                if (!$data = static::decodeChunked($data)) {
+                    Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to rebuild the chunked data received from server');
+                    throw new HttpException(PhpXmlRpc::$xmlrpcstr['dechunk_fail'], PhpXmlRpc::$xmlrpcerr['dechunk_fail'], null, $httpResponse['status_code']);
                 }
             }
 
@@ -229,12 +248,12 @@ class Http
                                 Logger::instance()->debugMessage("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
                             }
                         } else {
-                            error_log('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to decode the deflated data received from server');
-                            throw new \Exception(PhpXmlRpc::$xmlrpcstr['decompress_fail'], PhpXmlRpc::$xmlrpcerr['decompress_fail']);
+                            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to decode the deflated data received from server');
+                            throw new HttpException(PhpXmlRpc::$xmlrpcstr['decompress_fail'], PhpXmlRpc::$xmlrpcerr['decompress_fail'], null, $httpResponse['status_code']);
                         }
                     } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
-                        throw new \Exception(PhpXmlRpc::$xmlrpcstr['cannot_decompress'], PhpXmlRpc::$xmlrpcerr['cannot_decompress']);
+                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
+                        throw new HttpException(PhpXmlRpc::$xmlrpcstr['cannot_decompress'], PhpXmlRpc::$xmlrpcerr['cannot_decompress'], null, $httpResponse['status_code']);
                     }
                 }
             }
index 77e0e14..bee7b08 100644 (file)
@@ -2,12 +2,16 @@
 
 namespace PhpXmlRpc\Helper;
 
+/**
+ * @todo implement an interface
+ * @todo make constructor private to force users to go through `instance()` ?
+ */
 class Logger
 {
     protected static $instance = null;
 
     /**
-     * This class is singleton, so that later we can move to DI patterns.
+     * This class can be used as singleton, so that later we can move to DI patterns.
      *
      * @return Logger
      */
@@ -28,7 +32,7 @@ class Logger
      * @param string $message
      * @param string $encoding
      */
-    public function debugMessage($message, $encoding=null)
+    public function debugMessage($message, $encoding = null)
     {
         // US-ASCII is a warning for PHP and a fatal for HHVM
         if ($encoding == 'US-ASCII') {
@@ -36,7 +40,14 @@ class Logger
         }
 
         if (PHP_SAPI != 'cli') {
-            $flags = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE;
+            $flags = ENT_COMPAT;
+            // avoid warnings on php < 5.4...
+            if (defined('ENT_HTML401')) {
+                $flags =  $flags | ENT_HTML401;
+            }
+            if (defined('ENT_SUBSTITUTE')) {
+                $flags =  $flags | ENT_SUBSTITUTE;
+            }
             if ($encoding != null) {
                 print "<PRE>\n".htmlentities($message, $flags, $encoding)."\n</PRE>";
             } else {
@@ -49,4 +60,13 @@ class Logger
         // let the user see this now in case there's a time out later...
         flush();
     }
+
+    /**
+     * Writes a message to the error log
+     * @param string $message
+     */
+    public function errorLog($message)
+    {
+        error_log($message);
+    }
 }
index b7d137f..f6e79a9 100644 (file)
@@ -7,30 +7,48 @@ use PhpXmlRpc\Value;
 
 /**
  * Deals with parsing the XML.
+ * @see http://xmlrpc.com/spec.md
+ *
+ * @todo implement an interface to allow for alternative implementations
+ *       - make access to $_xh protected, return more high-level data structures
+ *       - add parseRequest, parseResponse, parseValue methods
+ * @todo if iconv() or mb_string() are available, we could allow to convert the received xml to a custom charset encoding
+ *       while parsing, which is faster than doing it later by going over the rebuilt data structure
  */
 class XMLParser
 {
-    // used to store state during parsing
-    // quick explanation of components:
-    //   ac - used to accumulate values
-    //   stack - array with genealogy of xml elements names:
-    //           used to validate nesting of xmlrpc elements
-    //   valuestack - array used for parsing arrays and structs
-    //   lv - used to indicate "looking for a value": implements
-    //        the logic to allow values with no types to be strings
-    //   isf - used to indicate a parsing fault (2) or xmlrpc response fault (1)
-    //   isf_reason - used for storing xmlrpc response fault string
-    //   method - used to store method name
-    //   params - used to store parameters in method calls
-    //   pt - used to store the type of each received parameter. Useful if parameters are automatically decoded to php values
-    //   rt  - 'methodcall or 'methodresponse'
+    const RETURN_XMLRPCVALS = 'xmlrpcvals';
+    const RETURN_EPIVALS = 'epivals';
+    const RETURN_PHP = 'phpvals';
+
+    const ACCEPT_REQUEST = 1;
+    const ACCEPT_RESPONSE = 2;
+    const ACCEPT_VALUE = 4;
+    const ACCEPT_FAULT = 8;
+
+    // Used to store state during parsing and to pass parsing results to callers.
+    // Quick explanation of components:
+    //  private:
+    //    ac - used to accumulate values
+    //    stack - array with genealogy of xml elements names used to validate nesting of xmlrpc elements
+    //    valuestack - array used for parsing arrays and structs
+    //    lv - used to indicate "looking for a value": implements the logic to allow values with no types to be strings
+    //  public:
+    //    isf - used to indicate an xml parsing fault (3), invalid xmlrpc fault (2) or xmlrpc response fault (1)
+    //    isf_reason - used for storing xmlrpc response fault string
+    //    value - used to store the value in responses
+    //    method - used to store method name in requests
+    //    params - used to store parameters in requests
+    //    pt - used to store the type of each received parameter. Useful if parameters are automatically decoded to php values
+    //    rt  - 'methodcall', 'methodresponse', 'value' or 'fault' (the last one used only in EPI emulation mode)
     public $_xh = array(
         'ac' => '',
         'stack' => array(),
         'valuestack' => array(),
         'isf' => 0,
         'isf_reason' => '',
-        'method' => false, // so we can check later if we got a methodname or not
+        'value' => null,
+        'method' => false,
         'params' => array(),
         'pt' => array(),
         'rt' => '',
@@ -60,27 +78,132 @@ class XMLParser
         'EX:NIL' => array('VALUE'), // only used when extension activated
     );
 
+    /** @var array $parsing_options */
+    protected $parsing_options = array();
+    /** @var int $accept self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE by default */
+    protected $accept = 3;
+    /** @var int $maxChunkLength 4 MB by default. Any value below 10MB should be good */
+    protected $maxChunkLength = 4194304;
+
+    /**
+     * @param array $options passed to the xml parser
+     */
+    public function __construct(array $options = array())
+    {
+        $this->parsing_options = $options;
+    }
+
+    /**
+     * @param string $data
+     * @param string $returnType
+     * @param int $accept a bit-combination of self::ACCEPT_REQUEST, self::ACCEPT_RESPONSE, self::ACCEPT_VALUE
+     * @param array $options
+     */
+    public function parse($data, $returnType = self::RETURN_XMLRPCVALS, $accept = 3, $options = array())
+    {
+        $this->_xh = array(
+            'ac' => '',
+            'stack' => array(),
+            'valuestack' => array(),
+            'isf' => 0,
+            'isf_reason' => '',
+            'value' => null,
+            'method' => false, // so we can check later if we got a methodname or not
+            'params' => array(),
+            'pt' => array(),
+            'rt' => '',
+        );
+
+        $len = strlen($data);
+
+        // we test for empty documents here to save on resource allocation and simply the chunked-parsing loop below
+        if ($len == 0) {
+            $this->_xh['isf'] = 3;
+            $this->_xh['isf_reason'] = 'XML error 5: empty document';
+            return;
+        }
+
+        $parser = xml_parser_create();
+
+        foreach ($this->parsing_options as $key => $val) {
+            xml_parser_set_option($parser, $key, $val);
+        }
+        foreach ($options as $key => $val) {
+            xml_parser_set_option($parser, $key, $val);
+        }
+        // always set this, in case someone tries to disable it via options...
+        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 1);
+
+        xml_set_object($parser, $this);
+
+        switch($returnType) {
+            case self::RETURN_PHP:
+                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
+                break;
+            case self::RETURN_EPIVALS:
+                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi');
+                break;
+            default:
+                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
+        }
+
+        xml_set_character_data_handler($parser, 'xmlrpc_cd');
+        xml_set_default_handler($parser, 'xmlrpc_dh');
+
+        $this->accept = $accept;
+
+        // @see ticket #70 - we have to parse big xml docks in chunks to avoid errors
+        for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) {
+            $chunk = substr($data, $offset, $this->maxChunkLength);
+            // error handling: xml not well formed
+            if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) {
+                $errCode = xml_get_error_code($parser);
+                $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode),
+                    xml_get_current_line_number($parser), xml_get_current_column_number($parser));
+
+                $this->_xh['isf'] = 3;
+                $this->_xh['isf_reason'] = $errStr;
+                break;
+            }
+        }
+
+        xml_parser_free($parser);
+    }
+
     /**
      * xml parser handler function for opening element tags.
+     * @internal
+     * @param resource $parser
+     * @param string $name
+     * @param $attrs
+     * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead
      */
     public function xmlrpc_se($parser, $name, $attrs, $acceptSingleVals = false)
     {
         // if invalid xmlrpc already detected, skip all processing
         if ($this->_xh['isf'] < 2) {
+
             // check for correct element nesting
-            // top level element can only be of 2 types
-            /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
-            ///       there is only a single top level element in xml anyway
             if (count($this->_xh['stack']) == 0) {
-                if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
-                        $name != 'VALUE' && !$acceptSingleVals)
-                ) {
+                // top level element can only be of 2 types
+                /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
+                ///       there is only a single top level element in xml anyway
+                // BC
+                if ($acceptSingleVals === false) {
+                    $accept = $this->accept;
+                } else {
+                    $accept = self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE | self::ACCEPT_VALUE;
+                }
+                if (($name == 'METHODCALL' && ($accept & self::ACCEPT_REQUEST)) ||
+                    ($name == 'METHODRESPONSE' && ($accept & self::ACCEPT_RESPONSE)) ||
+                    ($name == 'VALUE' && ($accept & self::ACCEPT_VALUE)) ||
+                    ($name == 'FAULT' && ($accept & self::ACCEPT_FAULT))) {
+                    $this->_xh['rt'] = strtolower($name);
+                } else {
                     $this->_xh['isf'] = 2;
-                    $this->_xh['isf_reason'] = 'missing top level xmlrpc element';
+                    $this->_xh['isf_reason'] = 'missing top level xmlrpc element. Found: ' . $name;
 
                     return;
-                } else {
-                    $this->_xh['rt'] = strtolower($name);
                 }
             } else {
                 // not top level element: see if parent is OK
@@ -105,13 +228,13 @@ class XMLParser
                 case 'I8':
                 case 'EX:I8':
                     if (PHP_INT_SIZE === 4) {
-                        /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
+                        // INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
                         $this->_xh['isf'] = 2;
                         $this->_xh['isf_reason'] = "Received i8 element but php is compiled in 32 bit mode";
 
                         return;
                     }
-                // fall through voluntarily
+                    // fall through voluntarily
                 case 'I4':
                 case 'INT':
                 case 'STRING':
@@ -131,7 +254,7 @@ class XMLParser
                 case 'STRUCT':
                 case 'ARRAY':
                     if ($this->_xh['vt'] != 'value') {
-                        //two data elements inside a value: an error occurred!
+                        // two data elements inside a value: an error occurred!
                         $this->_xh['isf'] = 2;
                         $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
 
@@ -151,7 +274,7 @@ class XMLParser
                     break;
                 case 'DATA':
                     if ($this->_xh['vt'] != 'data') {
-                        //two data elements inside a value: an error occurred!
+                        // two data elements inside a value: an error occurred!
                         $this->_xh['isf'] = 2;
                         $this->_xh['isf_reason'] = "found two data elements inside an array element";
 
@@ -171,7 +294,8 @@ class XMLParser
                     $this->_xh['isf'] = 1;
                     break;
                 case 'MEMBER':
-                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = ''; // set member name to null, in case we do not find in the xml later on
+                    // set member name to null, in case we do not find in the xml later on
+                    $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = '';
                     //$this->_xh['ac']='';
                 // Drop trough intentionally
                 case 'PARAM':
@@ -182,7 +306,7 @@ class XMLParser
                 case 'EX:NIL':
                     if (PhpXmlRpc::$xmlrpc_null_extension) {
                         if ($this->_xh['vt'] != 'value') {
-                            //two data elements inside a value: an error occurred!
+                            // two data elements inside a value: an error occurred!
                             $this->_xh['isf'] = 2;
                             $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value";
 
@@ -194,7 +318,7 @@ class XMLParser
                 // we do not support the <NIL/> extension, so
                 // drop through intentionally
                 default:
-                    /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
+                    // INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
                     $this->_xh['isf'] = 2;
                     $this->_xh['isf_reason'] = "found not-xmlrpc xml element $name";
                     break;
@@ -211,7 +335,12 @@ class XMLParser
     }
 
     /**
-     * Used in decoding xml chunks that might represent single xmlrpc values.
+     * xml parser handler function for opening element tags.
+     * Used in decoding xml chunks that might represent single xmlrpc values as well as requests, responses.
+     * @deprecated
+     * @param resource $parser
+     * @param $name
+     * @param $attrs
      */
     public function xmlrpc_se_any($parser, $name, $attrs)
     {
@@ -220,8 +349,12 @@ class XMLParser
 
     /**
      * xml parser handler function for close element tags.
+     * @internal
+     * @param resource $parser
+     * @param string $name
+     * @param int $rebuildXmlrpcvals >1 for rebuilding xmlrpcvals, 0 for rebuilding php values, -1 for xmlrpc-extension compatibility
      */
-    public function xmlrpc_ee($parser, $name, $rebuildXmlrpcvals = true)
+    public function xmlrpc_ee($parser, $name, $rebuildXmlrpcvals = 1)
     {
         if ($this->_xh['isf'] < 2) {
             // push this element name from stack
@@ -238,7 +371,7 @@ class XMLParser
                         $this->_xh['vt'] = Value::$xmlrpcString;
                     }
 
-                    if ($rebuildXmlrpcvals) {
+                    if ($rebuildXmlrpcvals > 0) {
                         // build the xmlrpc val out of the data received, and substitute it
                         $temp = new Value($this->_xh['value'], $this->_xh['vt']);
                         // in case we got info about underlying php class, save it
@@ -246,27 +379,33 @@ class XMLParser
                         if (isset($this->_xh['php_class'])) {
                             $temp->_php_class = $this->_xh['php_class'];
                         }
-                        // check if we are inside an array or struct:
-                        // if value just built is inside an array, let's move it into array on the stack
-                        $vscount = count($this->_xh['valuestack']);
-                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
-                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $temp;
-                        } else {
-                            $this->_xh['value'] = $temp;
+                        $this->_xh['value'] = $temp;
+                    } elseif ($rebuildXmlrpcvals < 0) {
+                        if ($this->_xh['vt'] == Value::$xmlrpcDateTime) {
+                            $this->_xh['value'] = (object)array(
+                                'xmlrpc_type' => 'datetime',
+                                'scalar' => $this->_xh['value'],
+                                'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($this->_xh['value'])
+                            );
+                        } elseif ($this->_xh['vt'] == Value::$xmlrpcBase64) {
+                            $this->_xh['value'] = (object)array(
+                                'xmlrpc_type' => 'base64',
+                                'scalar' => $this->_xh['value']
+                            );
                         }
                     } else {
-                        /// @todo this needs to treat correctly php-serialized objects,
+                        /// @todo this should handle php-serialized objects,
                         /// since std deserializing is done by php_xmlrpc_decode,
                         /// which we will not be calling...
-                        if (isset($this->_xh['php_class'])) {
-                        }
+                        //if (isset($this->_xh['php_class'])) {
+                        //}
+                    }
 
-                        // check if we are inside an array or struct:
-                        // if value just built is inside an array, let's move it into array on the stack
-                        $vscount = count($this->_xh['valuestack']);
-                        if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
-                            $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value'];
-                        }
+                    // check if we are inside an array or struct:
+                    // if value just built is inside an array, let's move it into array on the stack
+                    $vscount = count($this->_xh['valuestack']);
+                    if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') {
+                        $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value'];
                     }
                     break;
                 case 'BOOLEAN':
@@ -285,7 +424,7 @@ class XMLParser
                         $this->_xh['value'] = $this->_xh['ac'];
                     } elseif ($name == 'DATETIME.ISO8601') {
                         if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $this->_xh['ac'])) {
-                            error_log('XML-RPC: ' . __METHOD__ . ': invalid value received in DATETIME: ' . $this->_xh['ac']);
+                            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in DATETIME: ' . $this->_xh['ac']);
                         }
                         $this->_xh['vt'] = Value::$xmlrpcDateTime;
                         $this->_xh['value'] = $this->_xh['ac'];
@@ -304,7 +443,7 @@ class XMLParser
                         } else {
                             // log if receiving something strange, even though we set the value to false anyway
                             if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') != 0) {
-                                error_log('XML-RPC: ' . __METHOD__ . ': invalid value received in BOOLEAN: ' . $this->_xh['ac']);
+                                Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in BOOLEAN: ' . $this->_xh['ac']);
                             }
                             $this->_xh['value'] = false;
                         }
@@ -314,7 +453,7 @@ class XMLParser
                         // NOTE: regexp could be much stricter than this...
                         if (!preg_match('/^[+-eE0123456789 \t.]+$/', $this->_xh['ac'])) {
                             /// @todo: find a better way of throwing an error than this!
-                            error_log('XML-RPC: ' . __METHOD__ . ': non numeric value received in DOUBLE: ' . $this->_xh['ac']);
+                            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in DOUBLE: ' . $this->_xh['ac']);
                             $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
                         } else {
                             // it's ok, add it on
@@ -325,7 +464,7 @@ class XMLParser
                         // we must check that only 0123456789-<space> are characters here
                         if (!preg_match('/^[+-]?[0123456789 \t]+$/', $this->_xh['ac'])) {
                             /// @todo find a better way of throwing an error than this!
-                            error_log('XML-RPC: ' . __METHOD__ . ': non numeric value received in INT: ' . $this->_xh['ac']);
+                            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in INT: ' . $this->_xh['ac']);
                             $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND';
                         } else {
                             // it's ok, add it on
@@ -344,7 +483,7 @@ class XMLParser
                         $vscount = count($this->_xh['valuestack']);
                         $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value'];
                     } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml');
+                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml');
                     }
                     break;
                 case 'DATA':
@@ -367,7 +506,7 @@ class XMLParser
                         $this->_xh['params'][] = $this->_xh['value'];
                         $this->_xh['pt'][] = $this->_xh['vt'];
                     } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml');
+                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml');
                     }
                     break;
                 case 'METHODNAME':
@@ -397,14 +536,31 @@ class XMLParser
 
     /**
      * Used in decoding xmlrpc requests/responses without rebuilding xmlrpc Values.
+     * @internal
+     * @param resource $parser
+     * @param string $name
      */
     public function xmlrpc_ee_fast($parser, $name)
     {
-        $this->xmlrpc_ee($parser, $name, false);
+        $this->xmlrpc_ee($parser, $name, 0);
+    }
+
+    /**
+     * Used in decoding xmlrpc requests/responses while building xmlrpc-extension Values (plain php for all but base64 and datetime).
+     * @internal
+     * @param resource $parser
+     * @param string $name
+     */
+    public function xmlrpc_ee_epi($parser, $name)
+    {
+        $this->xmlrpc_ee($parser, $name, -1);
     }
 
     /**
      * xml parser handler function for character data.
+     * @internal
+     * @param resource $parser
+     * @param string $data
      */
     public function xmlrpc_cd($parser, $data)
     {
@@ -421,6 +577,9 @@ class XMLParser
     /**
      * xml parser handler function for 'other stuff', ie. not char data or
      * element start/end tag. In fact it only gets called on unknown entities...
+     * @internal
+     * @param $parser
+     * @param string data
      */
     public function xmlrpc_dh($parser, $data)
     {
@@ -431,7 +590,7 @@ class XMLParser
             }
         }
 
-        return true;
+        //return true;
     }
 
     /**
index 1f31b54..416d53f 100644 (file)
@@ -36,22 +36,22 @@ class PhpXmlRpc
 
     static public $xmlrpcstr = array(
         'unknown_method' => 'Unknown method',
-        'invalid_return' => 'Invalid return payload: enable debugging to examine incoming payload',
+        'invalid_return' => 'Invalid response payload (you can use the setDebug method to allow analysis of the response)',
         'incorrect_params' => 'Incorrect parameters passed to method',
         'introspect_unknown' => "Can't introspect: method unknown",
-        'http_error' => "Didn't receive 200 OK from remote server.",
-        'no_data' => 'No data received from server.',
-        'no_ssl' => 'No SSL support compiled in.',
+        'http_error' => "Didn't receive 200 OK from remote server",
+        'no_data' => 'No data received from server',
+        'no_ssl' => 'No SSL support compiled in',
         'curl_fail' => 'CURL error',
         'invalid_request' => 'Invalid request payload',
-        'no_curl' => 'No CURL support compiled in.',
+        'no_curl' => 'No CURL support compiled in',
         'server_error' => 'Internal server error',
         'multicall_error' => 'Received from server invalid multicall response',
         'multicall_notstruct' => 'system.multicall expected struct',
-        'multicall_nomethod' => 'missing methodName',
+        'multicall_nomethod' => 'Missing methodName',
         'multicall_notstring' => 'methodName is not a string',
-        'multicall_recursion' => 'recursive system.multicall forbidden',
-        'multicall_noparams' => 'missing params',
+        'multicall_recursion' => 'Recursive system.multicall forbidden',
+        'multicall_noparams' => 'Missing params',
         'multicall_notarray' => 'params is not an array',
 
         'cannot_decompress' => 'Received from server compressed HTTP and cannot decompress',
@@ -61,25 +61,24 @@ class PhpXmlRpc
         'server_decompress_fail' => 'Received from client invalid compressed HTTP request',
     );
 
-    // The charset encoding used by the server for received requests and
-    // by the client for received responses when received charset cannot be determined
-    // and mbstring extension is not enabled
+    // The charset encoding used by the server for received requests and by the client for received responses when
+    // received charset cannot be determined and mbstring extension is not enabled
     public static $xmlrpc_defencoding = "UTF-8";
 
-    // The list of encodings used by the server for requests and by the client for responses
-    // to detect the charset of the received payload when
+    // The list of encodings used by the server for requests and by the client for responses to detect the charset of
+    // the received payload when
     // - the charset cannot be determined by looking at http headers, xml declaration or BOM
     // - mbstring extension is enabled
     public static $xmlrpc_detectencodings = array();
 
     // The encoding used internally by PHP.
-    // String values received as xml will be converted to this, and php strings will be converted to xml
-    // as if having been coded with this.
+    // String values received as xml will be converted to this, and php strings will be converted to xml as if
+    // having been coded with this.
     // Valid also when defining names of xmlrpc methods
     public static $xmlrpc_internalencoding = "UTF-8";
 
     public static $xmlrpcName = "XML-RPC for PHP";
-    public static $xmlrpcVersion = "4.2.0";
+    public static $xmlrpcVersion = "4.7.0-dev";
 
     // let user errors start at 800
     public static $xmlrpcerruser = 800;
@@ -94,6 +93,9 @@ class PhpXmlRpc
 
     public static $xmlrpc_null_apache_encoding_ns = "http://ws.apache.org/xmlrpc/namespaces/extensions";
 
+    // number of decimal digits used to serialize Double values
+    public static $xmlpc_double_precision = 128;
+
     /**
      * A function to be used for compatibility with legacy code: it creates all global variables which used to be declared,
      * such as library version etc...
@@ -105,8 +107,8 @@ class PhpXmlRpc
             $GLOBALS[$name] = $value;
         }
 
-        // NB: all the variables exported into the global namespace below here do NOT guarantee 100%
-        // compatibility, as they are NOT reimported back during calls to importGlobals()
+        // NB: all the variables exported into the global namespace below here do NOT guarantee 100% compatibility,
+        // as they are NOT reimported back during calls to importGlobals()
 
         $reflection = new \ReflectionClass('PhpXmlRpc\Value');
         foreach ($reflection->getStaticProperties() as $name => $value) {
@@ -146,5 +148,4 @@ class PhpXmlRpc
             }
         }
     }
-
 }
index 4714489..9841c01 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace PhpXmlRpc;
 
+use PhpXmlRpc\Exception\HttpException;
 use PhpXmlRpc\Helper\Charset;
 use PhpXmlRpc\Helper\Http;
 use PhpXmlRpc\Helper\Logger;
@@ -13,16 +14,62 @@ use PhpXmlRpc\Helper\XMLParser;
  */
 class Request
 {
+    protected static $logger;
+    protected static $parser;
+    protected static $charsetEncoder;
+
     /// @todo: do these need to be public?
     public $payload;
+    /** @internal */
     public $methodname;
+    /** @internal */
     public $params = array();
     public $debug = 0;
     public $content_type = 'text/xml';
 
     // holds data while parsing the response. NB: Not a full Response object
+    /** @deprecated will be removed in a future release */
     protected $httpResponse = array();
 
+    public function getLogger()
+    {
+        if (self::$logger === null) {
+            self::$logger = Logger::instance();
+        }
+        return self::$logger;
+    }
+
+    public static function setLogger($logger)
+    {
+        self::$logger = $logger;
+    }
+
+    public function getParser()
+    {
+        if (self::$parser === null) {
+            self::$parser = new XMLParser();
+        }
+        return self::$parser;
+    }
+
+    public static function setParser($parser)
+    {
+        self::$parser = $parser;
+    }
+
+    public function getCharsetEncoder()
+    {
+        if (self::$charsetEncoder === null) {
+            self::$charsetEncoder = Charset::instance();
+        }
+        return self::$charsetEncoder;
+    }
+
+    public function setCharsetEncoder($charsetEncoder)
+    {
+        self::$charsetEncoder = $charsetEncoder;
+    }
+
     /**
      * @param string $methodName the name of the method to invoke
      * @param Value[] $params array of parameters to be passed to the method (NB: Value objects, not plain php values)
@@ -35,6 +82,11 @@ class Request
         }
     }
 
+    /**
+     * @internal this function will become protected in the future
+     * @param string $charsetEncoding
+     * @return string
+     */
     public function xml_header($charsetEncoding = '')
     {
         if ($charsetEncoding != '') {
@@ -44,11 +96,19 @@ class Request
         }
     }
 
+    /**
+     * @internal this function will become protected in the future
+     * @return string
+     */
     public function xml_footer()
     {
         return '</methodCall>';
     }
 
+    /**
+     * @internal this function will become protected in the future
+     * @param string $charsetEncoding
+     */
     public function createPayload($charsetEncoding = '')
     {
         if ($charsetEncoding != '') {
@@ -57,7 +117,7 @@ class Request
             $this->content_type = 'text/xml';
         }
         $this->payload = $this->xml_header($charsetEncoding);
-        $this->payload .= '<methodName>' . Charset::instance()->encodeEntities(
+        $this->payload .= '<methodName>' . $this->getCharsetEncoder()->encodeEntities(
             $this->methodname, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</methodName>\n";
         $this->payload .= "<params>\n";
         foreach ($this->params as $p) {
@@ -150,18 +210,18 @@ class Request
      *      because we cannot trust the caller to give us a valid pointer to an open file...
      *
      * @param resource $fp stream pointer
+     * @param bool $headersProcessed
+     * @param string $returnType
      *
      * @return Response
-     *
-     * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
      */
-    public function parseResponseFile($fp)
+    public function parseResponseFile($fp, $headersProcessed = false, $returnType = 'xmlrpcvals')
     {
         $ipd = '';
         while ($data = fread($fp, 32768)) {
             $ipd .= $data;
         }
-        return $this->parseResponse($ipd);
+        return $this->parseResponse($ipd, $headersProcessed, $returnType);
     }
 
     /**
@@ -176,17 +236,19 @@ class Request
      *                           'phpvals'
      *
      * @return Response
+     *
+     * @todo parsing Responses is not really the responsibility of the Request class. Maybe of the Client...
      */
-    public function parseResponse($data = '', $headersProcessed = false, $returnType = 'xmlrpcvals')
+    public function parseResponse($data = '', $headersProcessed = false, $returnType = XMLParser::RETURN_XMLRPCVALS)
     {
         if ($this->debug) {
-            Logger::instance()->debugMessage("---GOT---\n$data\n---END---");
+            $this->getLogger()->debugMessage("---GOT---\n$data\n---END---");
         }
 
         $this->httpResponse = array('raw_data' => $data, 'headers' => array(), 'cookies' => array());
 
         if ($data == '') {
-            error_log('XML-RPC: ' . __METHOD__ . ': no response received from server.');
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': no response received from server.');
             return new Response(0, PhpXmlRpc::$xmlrpcerr['no_data'], PhpXmlRpc::$xmlrpcstr['no_data']);
         }
 
@@ -195,20 +257,19 @@ class Request
             $httpParser = new Http();
             try {
                 $this->httpResponse = $httpParser->parseResponseHeaders($data, $headersProcessed, $this->debug);
-            } catch(\Exception $e) {
-                $r = new Response(0, $e->getCode(), $e->getMessage());
+            } catch (HttpException $e) {
                 // failed processing of HTTP response headers
                 // save into response obj the full payload received, for debugging
-                $r->raw_data = $data;
-
-                return $r;
+                return new Response(0, $e->getCode(), $e->getMessage(), '', array('raw_data' => $data, 'status_code', $e->statusCode()));
+            } catch(\Exception $e) {
+                return new Response(0, $e->getCode(), $e->getMessage(), '', array('raw_data' => $data));
             }
         }
 
         // be tolerant of extra whitespace in response body
         $data = trim($data);
 
-        /// @todo return an error msg if $data=='' ?
+        /// @todo return an error msg if $data == '' ?
 
         // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
         // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
@@ -226,120 +287,96 @@ class Request
                 $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
                 $end = strpos($data, '-->', $start);
                 $comments = substr($data, $start, $end - $start);
-                Logger::instance()->debugMessage("---SERVER DEBUG INFO (DECODED) ---\n\t" .
+                $this->getLogger()->debugMessage("---SERVER DEBUG INFO (DECODED) ---\n\t" .
                     str_replace("\n", "\n\t", base64_decode($comments)) . "\n---END---", $respEncoding);
             }
         }
 
-        // if user wants back raw xml, give it to him
+        // if user wants back raw xml, give it to her
         if ($returnType == 'xml') {
-            $r = new Response($data, 0, '', 'xml');
-            $r->hdrs = $this->httpResponse['headers'];
-            $r->_cookies = $this->httpResponse['cookies'];
-            $r->raw_data = $this->httpResponse['raw_data'];
-
-            return $r;
+            return new Response($data, 0, '', 'xml', $this->httpResponse);
         }
 
         if ($respEncoding != '') {
 
             // Since parsing will fail if charset is not specified in the xml prologue,
             // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
-            // The following code might be better for mb_string enabled installs, but
-            // makes the lib about 200% slower...
+            // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower...
             //if (!is_valid_charset($respEncoding, array('UTF-8')))
             if (!in_array($respEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
                 if ($respEncoding == 'ISO-8859-1') {
                     $data = utf8_encode($data);
                 } else {
+
                     if (extension_loaded('mbstring')) {
                         $data = mb_convert_encoding($data, 'UTF-8', $respEncoding);
                     } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received response: ' . $respEncoding);
+                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received response: ' . $respEncoding);
                     }
                 }
             }
         }
 
-        $parser = xml_parser_create();
-        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
-        // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
-        // the xml parser to give us back data in the expected charset.
-        // What if internal encoding is not in one of the 3 allowed?
-        // we use the broadest one, ie. utf8
-        // This allows to send data which is native in various charset,
-        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
+        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
+        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
+        // This allows to send data which is native in various charset, by extending xmlrpc_encode_entities() and
+        // setting xmlrpc_internalencoding
         if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
-            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
+            /// @todo emit a warning
+            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
         } else {
-            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding);
+            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
         }
 
-        $xmlRpcParser = new XMLParser();
-        xml_set_object($parser, $xmlRpcParser);
+        $xmlRpcParser = $this->getParser();
+        $xmlRpcParser->parse($data, $returnType, XMLParser::ACCEPT_RESPONSE, $options);
 
-        if ($returnType == 'phpvals') {
-            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
-        } else {
-            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
-        }
+        // first error check: xml not well formed
+        if ($xmlRpcParser->_xh['isf'] > 2) {
 
-        xml_set_character_data_handler($parser, 'xmlrpc_cd');
-        xml_set_default_handler($parser, 'xmlrpc_dh');
+            // BC break: in the past for some cases we used the error message: 'XML error at line 1, check URL'
+
+            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
+                PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason'], '',
+                $this->httpResponse
+            );
 
-        // first error check: xml not well formed
-        if (!xml_parse($parser, $data, count($data))) {
-            // thanks to Peter Kocks <peter.kocks@baygate.com>
-            if ((xml_get_current_line_number($parser)) == 1) {
-                $errStr = 'XML error at line 1, check URL';
-            } else {
-                $errStr = sprintf('XML error: %s at line %d, column %d',
-                    xml_error_string(xml_get_error_code($parser)),
-                    xml_get_current_line_number($parser), xml_get_current_column_number($parser));
-            }
-            error_log($errStr);
-            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' (' . $errStr . ')');
-            xml_parser_free($parser);
             if ($this->debug) {
-                print $errStr;
+                print $xmlRpcParser->_xh['isf_reason'];
             }
-            $r->hdrs = $this->httpResponse['headers'];
-            $r->_cookies = $this->httpResponse['cookies'];
-            $r->raw_data = $this->httpResponse['raw_data'];
-
-            return $r;
         }
-        xml_parser_free($parser);
         // second error check: xml well formed but not xml-rpc compliant
-        if ($xmlRpcParser->_xh['isf'] > 1) {
+        elseif ($xmlRpcParser->_xh['isf'] == 2) {
+            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
+                PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason'], '',
+                $this->httpResponse
+            );
+
             if ($this->debug) {
                 /// @todo echo something for user?
             }
-
-            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
-                PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
         }
         // third error check: parsing of the response has somehow gone boink.
-        // NB: shall we omit this check, since we trust the parsing code?
-        elseif ($returnType == 'xmlrpcvals' && !is_object($xmlRpcParser->_xh['value'])) {
+        /// @todo shall we omit this check, since we trust the parsing code?
+        elseif ($returnType == XMLParser::RETURN_XMLRPCVALS && !is_object($xmlRpcParser->_xh['value'])) {
             // something odd has happened
             // and it's time to generate a client side error
             // indicating something odd went on
-            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'],
-                PhpXmlRpc::$xmlrpcstr['invalid_return']);
+            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return'],
+                '', $this->httpResponse
+            );
         } else {
             if ($this->debug > 1) {
-                Logger::instance()->debugMessage(
+                $this->getLogger()->debugMessage(
                     "---PARSED---\n".var_export($xmlRpcParser->_xh['value'], true)."\n---END---"
                 );
             }
 
-            // note that using =& will raise an error if $xmlRpcParser->_xh['st'] does not generate an object.
-            $v = &$xmlRpcParser->_xh['value'];
+            $v = $xmlRpcParser->_xh['value'];
 
             if ($xmlRpcParser->_xh['isf']) {
                 /// @todo we should test here if server sent an int and a string, and/or coerce them into such...
-                if ($returnType == 'xmlrpcvals') {
+                if ($returnType == XMLParser::RETURN_XMLRPCVALS) {
                     $errNo_v = $v['faultCode'];
                     $errStr_v = $v['faultString'];
                     $errNo = $errNo_v->scalarval();
@@ -354,16 +391,12 @@ class Request
                     $errNo = -1;
                 }
 
-                $r = new Response(0, $errNo, $errStr);
+                $r = new Response(0, $errNo, $errStr, '', $this->httpResponse);
             } else {
-                $r = new Response($v, 0, '', $returnType);
+                $r = new Response($v, 0, '', $returnType, $this->httpResponse);
             }
         }
 
-        $r->hdrs = $this->httpResponse['headers'];
-        $r->_cookies = $this->httpResponse['cookies'];
-        $r->raw_data = $this->httpResponse['raw_data'];
-
         return $r;
     }
 
@@ -380,10 +413,10 @@ class Request
     /**
      * Enables/disables the echoing to screen of the xmlrpc responses received.
      *
-     * @param integer $in values 0, 1, 2 are supported
+     * @param integer $level values 0, 1, 2 are supported
      */
-    public function setDebug($in)
+    public function setDebug($level)
     {
-        $this->debug = $in;
+        $this->debug = $level;
     }
 }
index 48ebca8..f814d7b 100644 (file)
@@ -8,32 +8,54 @@ use PhpXmlRpc\Helper\Charset;
  * This class provides the representation of the response of an XML-RPC server.
  * Server-side, a server method handler will construct a Response and pass it as its return value.
  * An identical Response object will be returned by the result of an invocation of the send() method of the Client class.
+ *
+ * @property array $hdrs deprecated, use $httpResponse['headers']
+ * @property array _cookies deprecated, use $httpResponse['cookies']
+ * @property string $raw_data deprecated, use $httpResponse['raw_data']
  */
 class Response
 {
+    protected static $charsetEncoder;
+
     /// @todo: do these need to be public?
+    /** @internal */
     public $val = 0;
+    /** @internal */
     public $valtyp;
+    /** @internal */
     public $errno = 0;
+    /** @internal */
     public $errstr = '';
     public $payload;
-    public $hdrs = array();
-    public $_cookies = array();
     public $content_type = 'text/xml';
-    public $raw_data = '';
+    protected $httpResponse = array('headers' => array(), 'cookies' => array(), 'raw_data' => '', 'status_code' => null);
+
+    public function getCharsetEncoder()
+    {
+        if (self::$charsetEncoder === null) {
+            self::$charsetEncoder = Charset::instance();
+        }
+        return self::$charsetEncoder;
+    }
+
+    public function setCharsetEncoder($charsetEncoder)
+    {
+        self::$charsetEncoder = $charsetEncoder;
+    }
 
     /**
-     * @param mixed $val either a Value object, a php value or the xml serialization of an xmlrpc value (a string)
+     * @param Value|string|mixed $val either a Value object, a php value or the xml serialization of an xmlrpc value (a string)
      * @param integer $fCode set it to anything but 0 to create an error response. In that case, $val is discarded
      * @param string $fString the error string, in case of an error response
      * @param string $valType The type of $val passed in. Either 'xmlrpcvals', 'phpvals' or 'xml'. Leave empty to let
      *                        the code guess the correct type.
+     * @param array|null $httpResponse
      *
      * @todo add check that $val / $fCode / $fString is of correct type???
      *       NB: as of now we do not do it, since it might be either an xmlrpc value or a plain php val, or a complete
      *       xml chunk, depending on usage of Client::send() inside which creator is called...
      */
-    public function __construct($val, $fCode = 0, $fString = '', $valType = '')
+    public function __construct($val, $fCode = 0, $fString = '', $valType = '', $httpResponse = null)
     {
         if ($fCode != 0) {
             // error response
@@ -56,6 +78,10 @@ class Response
                 $this->valtyp = $valType;
             }
         }
+
+        if (is_array($httpResponse)) {
+            $this->httpResponse = array_merge(array('headers' => array(), 'cookies' => array(), 'raw_data' => '', 'status_code' => null), $httpResponse);
+        }
     }
 
     /**
@@ -98,11 +124,19 @@ class Response
      * It is up to the user-defined code to decide how to use the received cookies, and whether they have to be sent back
      * with the next request to the server (using Client::setCookie) or not.
      *
-     * @return array array of cookies received from the server
+     * @return array[] array of cookies received from the server
      */
     public function cookies()
     {
-        return $this->_cookies;
+        return $this->httpResponse['cookies'];
+    }
+
+    /**
+     * @return array array with keys 'headers', 'cookies', 'raw_data' and 'status_code'
+     */
+    public function httpResponse()
+    {
+        return $this->httpResponse;
     }
 
     /**
@@ -127,8 +161,7 @@ class Response
             $result = "<methodResponse>\n";
         }
         if ($this->errno) {
-            // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
-            // by xml-encoding non ascii chars
+            // Let non-ASCII response messages be tolerated by clients by xml-encoding non ascii chars
             $result .= "<fault>\n" .
                 "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
                 "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
@@ -141,7 +174,7 @@ class Response
                         $this->val .
                         "</param>\n</params>";
                 } else {
-                    /// @todo try to build something serializable?
+                    /// @todo try to build something serializable using the Encoder...
                     throw new \Exception('cannot serialize xmlrpc response objects whose content is native php values');
                 }
             } else {
@@ -155,4 +188,76 @@ class Response
 
         return $result;
     }
+
+    // BC layer
+
+    public function __get($name)
+    {
+        //trigger_error('getting property Response::' . $name . ' is deprecated', E_USER_DEPRECATED);
+
+        switch($name) {
+            case 'hdrs':
+                return $this->httpResponse['headers'];
+            case '_cookies':
+                return $this->httpResponse['cookies'];
+            case 'raw_data':
+                return $this->httpResponse['raw_data'];
+            default:
+                $trace = debug_backtrace();
+                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
+                return null;
+        }
+    }
+
+    public function __set($name, $value)
+    {
+        //trigger_error('setting property Response::' . $name . ' is deprecated', E_USER_DEPRECATED);
+
+        switch($name) {
+            case 'hdrs':
+                $this->httpResponse['headers'] = $value;
+                break;
+            case '_cookies':
+                $this->httpResponse['cookies'] = $value;
+                break;
+            case 'raw_data':
+                $this->httpResponse['raw_data'] = $value;
+                break;
+            default:
+                $trace = debug_backtrace();
+                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
+        }
+    }
+
+    public function __isset($name)
+    {
+        switch($name) {
+            case 'hdrs':
+                return isset($this->httpResponse['headers']);
+            case '_cookies':
+                return isset($this->httpResponse['cookies']);
+            case 'raw_data':
+                return isset($this->httpResponse['raw_data']);
+            default:
+                return false;
+        }
+    }
+
+    public function __unset($name)
+    {
+        switch($name) {
+            case 'hdrs':
+                unset($this->httpResponse['headers']);
+                break;
+            case '_cookies':
+                unset($this->httpResponse['cookies']);
+                break;
+            case 'raw_data':
+                unset($this->httpResponse['raw_data']);
+                break;
+            default:
+                $trace = debug_backtrace();
+                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
+        }
+    }
 }
index ab17168..1cc965f 100644 (file)
@@ -2,23 +2,24 @@
 
 namespace PhpXmlRpc;
 
-use PhpXmlRpc\Helper\XMLParser;
 use PhpXmlRpc\Helper\Charset;
+use PhpXmlRpc\Helper\Logger;
+use PhpXmlRpc\Helper\XMLParser;
 
 /**
  * Allows effortless implementation of XML-RPC servers
  */
 class Server
 {
-    /**
-     * Array defining php functions exposed as xmlrpc methods by this server.
-     */
-    protected $dmap = array();
+    protected static $logger;
+    protected static $parser;
+    protected static $charsetEncoder;
 
     /**
      * Defines how functions in dmap will be invoked: either using an xmlrpc request object
      * or plain php values.
      * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals'
+     * @todo create class constants for these
      */
     public $functions_parameters_type = 'xmlrpcvals';
 
@@ -79,21 +80,73 @@ class Server
     public $response_charset_encoding = '';
 
     /**
-     * Storage for internal debug info.
+     * Extra data passed at runtime to method handling functions. Used only by EPI layer
      */
-    protected $debug_info = '';
+    public $user_data = null;
 
     /**
-     * Extra data passed at runtime to method handling functions. Used only by EPI layer
+     * Array defining php functions exposed as xmlrpc methods by this server.
+     * @var array[] $dmap
      */
-    public $user_data = null;
+    protected $dmap = array();
+
+    /**
+     * Storage for internal debug info.
+     */
+    protected $debug_info = '';
 
     protected static $_xmlrpc_debuginfo = '';
     protected static $_xmlrpcs_occurred_errors = '';
     protected static $_xmlrpcs_prev_ehandler = '';
 
+    public function getLogger()
+    {
+        if (self::$logger === null) {
+            self::$logger = Logger::instance();
+        }
+        return self::$logger;
+    }
+
+    public static function setLogger($logger)
+    {
+        self::$logger = $logger;
+    }
+
+    public function getParser()
+    {
+        if (self::$parser === null) {
+            self::$parser = new XMLParser();
+        }
+        return self::$parser;
+    }
+
+    public static function setParser($parser)
+    {
+        self::$parser = $parser;
+    }
+
+    public function getCharsetEncoder()
+    {
+        if (self::$charsetEncoder === null) {
+            self::$charsetEncoder = Charset::instance();
+        }
+        return self::$charsetEncoder;
+    }
+
+    public function setCharsetEncoder($charsetEncoder)
+    {
+        self::$charsetEncoder = $charsetEncoder;
+    }
+
     /**
-     * @param array $dispatchMap the dispatch map with definition of exposed services
+     * @param array[] $dispatchMap the dispatch map with definition of exposed services
+     *                             Array keys are the names of the method names.
+     *                             Each array value is an array with the following members:
+     *                             - function (callable)
+     *                             - docstring (optional)
+     *                             - signature (array, optional)
+     *                             - signature_docs (array, optional)
+     *                             - parameters_type (string, optional)
      * @param boolean $serviceNow set to false to prevent the server from running upon construction
      */
     public function __construct($dispatchMap = null, $serviceNow = true)
@@ -142,19 +195,24 @@ class Server
     }
 
     /**
-     * Add a string to the debug info that can be later serialized by the server
-     * as part of the response message.
-     * Note that for best compatibility, the debug string should be encoded using
-     * the PhpXmlRpc::$xmlrpc_internalencoding character set.
+     * Add a string to the debug info that can be later serialized by the server as part of the response message.
+     * Note that for best compatibility, the debug string should be encoded using the PhpXmlRpc::$xmlrpc_internalencoding
+     * character set.
      *
      * @param string $msg
-     * @access public
      */
     public static function xmlrpc_debugmsg($msg)
     {
         static::$_xmlrpc_debuginfo .= $msg . "\n";
     }
 
+    /**
+     * Add a string to the debug info that will be later serialized by the server as part of the response message
+     * (base64 encoded, only when debug level >= 2)
+     *
+     * character set.
+     * @param string $msg
+     */
     public static function error_occurred($msg)
     {
         static::$_xmlrpcs_occurred_errors .= $msg . "\n";
@@ -179,7 +237,7 @@ class Server
             $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
         }
         if (static::$_xmlrpc_debuginfo != '') {
-            $out .= "<!-- DEBUG INFO:\n" . Charset::instance()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
+            $out .= "<!-- DEBUG INFO:\n" . $this->getCharsetEncoder()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
             // NB: a better solution MIGHT be to use CDATA, but we need to insert it
             // into return payload AFTER the beginning tag
             //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n";
@@ -217,10 +275,12 @@ class Server
         if (!$r) {
             // this actually executes the request
             $r = $this->parseRequest($data, $reqCharset);
-        }
 
-        // save full body of request into response, for more debugging usages
-        $r->raw_data = $rawData;
+            // save full body of request into response, for more debugging usages.
+            // Note that this is the _request_ data, not the response's own data, unlike what happens client-side
+            /// @todo try to move this injection to the resp. constructor or use a non-deprecated access method
+            $r->raw_data = $rawData;
+        }
 
         if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) {
             $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
@@ -232,8 +292,7 @@ class Server
             $payload = $payload . $this->serializeDebug($respCharset);
         }
 
-        // G. Giunta 2006-01-27: do not create response serialization if it has
-        // already happened. Helps building json magic
+        // Do not create response serialization if it has already happened. Helps building json magic
         if (empty($r->payload)) {
             $r->serialize($respCharset);
         }
@@ -254,6 +313,7 @@ class Server
             // http compression of output: only
             // if we can do it, and we want to do it, and client asked us to,
             // and php ini settings do not force it already
+            /// @todo check separately for gzencode and gzcompress functions, in case of polyfills
             $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
             if ($this->compress_response && function_exists('gzencode') && $respEncoding != ''
                 && $phpNoSelfCompress
@@ -269,13 +329,15 @@ class Server
                 }
             }
 
-            // do not output content-length header if php is compressing output for us:
-            // it will mess up measurements
+            // Do not output content-length header if php is compressing output for us:
+            // it will mess up measurements.
+            // Note that Apache/mod_php will add (and even alter!) the Content-Length header on its own, but only for
+            // responses up to 8000 bytes
             if ($phpNoSelfCompress) {
                 header('Content-Length: ' . (int)strlen($payload));
             }
         } else {
-            error_log('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
         }
 
         print $payload;
@@ -288,10 +350,16 @@ class Server
      * Add a method to the dispatch map.
      *
      * @param string $methodName the name with which the method will be made available
-     * @param string $function the php function that will get invoked
-     * @param array $sig the array of valid method signatures
+     * @param callable $function the php function that will get invoked
+     * @param array[] $sig the array of valid method signatures.
+     *                     Each element is one signature: an array of strings with at least one element
+     *                     First element = type of returned value. Elements 2..N = types of parameters 1..N
      * @param string $doc method documentation
-     * @param array $sigDoc the array of valid method signatures docs (one string per param, one for return type)
+     * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with
+     *                        descriptions instead of types (one string for return type, one per param)
+     *
+     * @todo raise a warning if the user tries to register a 'system.' method
+     * @todo allow setting parameters_type
      */
     public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false)
     {
@@ -313,7 +381,7 @@ class Server
      * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions
      * @param array $sigs array of known signatures to match against
      *
-     * @return array
+     * @return array int, string
      */
     protected function verifySignature($in, $sigs)
     {
@@ -362,14 +430,14 @@ class Server
     /**
      * Parse http headers received along with xmlrpc request. If needed, inflate request.
      *
-     * @return mixed Response|null on success or an error Response
+     * @return Response|null null on success or an error Response
      */
     protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
     {
         // check if $_SERVER is populated: it might have been disabled via ini file
         // (this is true even when in CLI mode)
         if (count($_SERVER) == 0) {
-            error_log('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
         }
 
         if ($this->debug > 1) {
@@ -387,6 +455,8 @@ class Server
             $contentEncoding = '';
         }
 
+        $rawData = $data;
+
         // check if request body has been compressed and decompress it
         if ($contentEncoding != '' && strlen($data)) {
             if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
@@ -403,12 +473,16 @@ class Server
                             $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
                         }
                     } else {
-                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], PhpXmlRpc::$xmlrpcstr['server_decompress_fail']);
+                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'],
+                            PhpXmlRpc::$xmlrpcstr['server_decompress_fail'], '', array('raw_data' => $rawData)
+                        );
 
                         return $r;
                     }
                 } else {
-                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], PhpXmlRpc::$xmlrpcstr['server_cannot_decompress']);
+                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'],
+                        PhpXmlRpc::$xmlrpcstr['server_cannot_decompress'], '', array('raw_data' => $rawData)
+                    );
 
                     return $r;
                 }
@@ -453,7 +527,7 @@ class Server
         $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
             $data);
 
-        return;
+        return null;
     }
 
     /**
@@ -466,14 +540,20 @@ class Server
      * @return Response
      *
      * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
+     *
+     * @internal this function will become protected in the future
+     * @todo either rename this function or move the 'execute' part out of it...
      */
     public function parseRequest($data, $reqEncoding = '')
     {
         // decompose incoming XML into request structure
 
         if ($reqEncoding != '') {
-            // Since parsing will fail if charset is not specified in the xml prologue,
-            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
+            // Since parsing will fail if
+            // - charset is not specified in the xml prologue,
+            // - the encoding is not UTF8 and
+            // - there are non-ascii chars in the text,
+            // we try to work round that...
             // The following code might be better for mb_string enabled installs, but
             // makes the lib about 200% slower...
             //if (!is_valid_charset($reqEncoding, array('UTF-8')))
@@ -484,56 +564,45 @@ class Server
                     if (extension_loaded('mbstring')) {
                         $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
                     } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
+                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
                     }
                 }
             }
         }
 
-        $parser = xml_parser_create();
-        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
-        // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
-        // the xml parser to give us back data in the expected charset
-        // What if internal encoding is not in one of the 3 allowed?
-        // we use the broadest one, ie. utf8
+        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
+        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
         // This allows to send data which is native in various charset,
         // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
         if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
-            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
+            /// @todo emit a warning
+            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
         } else {
-            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding);
+            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
         }
 
-        $xmlRpcParser = new XMLParser();
-        xml_set_object($parser, $xmlRpcParser);
-
-        if ($this->functions_parameters_type != 'xmlrpcvals') {
-            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
-        } else {
-            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
-        }
-        xml_set_character_data_handler($parser, 'xmlrpc_cd');
-        xml_set_default_handler($parser, 'xmlrpc_dh');
-        if (!xml_parse($parser, $data, 1)) {
-            // return XML error as a faultCode
+        $xmlRpcParser = $this->getParser();
+        $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options);
+        if ($xmlRpcParser->_xh['isf'] > 2) {
+            // (BC) we return XML error as a faultCode
+            preg_match('/^XML error ([0-9]+)/', $xmlRpcParser->_xh['isf_reason'], $matches);
             $r = new Response(0,
-                PhpXmlRpc::$xmlrpcerrxml + xml_get_error_code($parser),
-                sprintf('XML error: %s at line %d, column %d',
-                    xml_error_string(xml_get_error_code($parser)),
-                    xml_get_current_line_number($parser), xml_get_current_column_number($parser)));
-            xml_parser_free($parser);
+                PhpXmlRpc::$xmlrpcerrxml + $matches[1],
+                $xmlRpcParser->_xh['isf_reason']);
         } elseif ($xmlRpcParser->_xh['isf']) {
-            xml_parser_free($parser);
             $r = new Response(0,
                 PhpXmlRpc::$xmlrpcerr['invalid_request'],
                 PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
         } else {
-            xml_parser_free($parser);
             // small layering violation in favor of speed and memory usage:
             // we should allow the 'execute' method handle this, but in the
             // most common scenario (xmlrpc values type server with some methods
             // registered as phpvals) that would mean a useless encode+decode pass
-            if ($this->functions_parameters_type != 'xmlrpcvals' || (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) && ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] == 'phpvals'))) {
+            if ($this->functions_parameters_type != 'xmlrpcvals' ||
+                (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) &&
+                    ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] != 'xmlrpcvals')
+                )
+            ) {
                 if ($this->debug > 1) {
                     $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++");
                 }
@@ -559,9 +628,9 @@ class Server
     /**
      * Execute a method invoked by the client, checking parameters used.
      *
-     * @param mixed $req either a Request obj or a method name
-     * @param array $params array with method parameters as php types (if m is method name only)
-     * @param array $paramTypes array with xmlrpc types of method parameters (if m is method name only)
+     * @param Request|string $req either a Request obj or a method name
+     * @param mixed[] $params array with method parameters as php types (only if m is method name)
+     * @param string[] $paramTypes array with xmlrpc types of method parameters (only if m is method name)
      *
      * @return Response
      *
@@ -577,7 +646,7 @@ class Server
         } else {
             $methName = $req;
         }
-        $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
+        $sysCall = $this->isSyscall($methName);
         $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
 
         if (!isset($dmap[$methName]['function'])) {
@@ -625,7 +694,7 @@ class Server
 
         // verify that function to be invoked is in fact callable
         if (!is_callable($func)) {
-            error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
+            $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
             return new Response(
                 0,
                 PhpXmlRpc::$xmlrpcerr['server_error'],
@@ -648,7 +717,7 @@ class Server
                     $r = call_user_func($func, $req);
                 }
                 if (!is_a($r, 'PhpXmlRpc\Response')) {
-                    error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
+                    $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
                     if (is_a($r, 'PhpXmlRpc\Value')) {
                         $r = new Response($r);
                     } else {
@@ -669,7 +738,7 @@ class Server
                     if ($this->functions_parameters_type == 'epivals') {
                         $r = call_user_func_array($func, array($methName, $params, $this->user_data));
                         // mimic EPI behaviour: if we get an array that looks like an error, make it
-                        // an eror response
+                        // an error response
                         if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
                             $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
                         } else {
@@ -703,7 +772,6 @@ class Server
                         }
                     }
                     throw $e;
-                    break;
                 case 1:
                     $r = new Response(0, $e->getCode(), $e->getMessage());
                     break;
@@ -747,13 +815,32 @@ class Server
         }
     }
 
-    /* Functions that implement system.XXX methods of xmlrpc servers */
+    /**
+     * @param string $methName
+     * @return bool
+     */
+    protected function isSyscall($methName)
+    {
+        return (strpos($methName, "system.") === 0);
+    }
+
+    /**
+     * @return array[]
+     */
+    public function getDispatchMap()
+    {
+        return $this->dmap;
+    }
 
     /**
-     * @return array
+     * @return array[]
      */
     public function getSystemDispatchMap()
     {
+        if (!$this->allow_system_funcs) {
+            return array();
+        }
+
         return array(
             'system.listMethods' => array(
                 'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
@@ -784,14 +871,16 @@ class Server
             'system.getCapabilities' => array(
                 'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
                 'signature' => array(array(Value::$xmlrpcStruct)),
-                'docstring' => 'This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to',
+                'docstring' => 'This method lists all the capabilities that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to',
                 'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
             ),
         );
     }
 
+    /* Functions that implement system.XXX methods of xmlrpc servers */
+
     /**
-     * @return array
+     * @return array[]
      */
     public function getCapabilities()
     {
@@ -825,27 +914,40 @@ class Server
         return $outAr;
     }
 
+    /**
+     * @param Server $server
+     * @param Request $req
+     * @return Response
+     */
     public static function _xmlrpcs_getCapabilities($server, $req = null)
     {
         $encoder = new Encoder();
         return new Response($encoder->encode($server->getCapabilities()));
     }
 
-    public static function _xmlrpcs_listMethods($server, $req = null) // if called in plain php values mode, second param is missing
+    /**
+     * @param Server $server
+     * @param Request $req if called in plain php values mode, second param is missing
+     * @return Response
+     */
+    public static function _xmlrpcs_listMethods($server, $req = null)
     {
         $outAr = array();
         foreach ($server->dmap as $key => $val) {
             $outAr[] = new Value($key, 'string');
         }
-        if ($server->allow_system_funcs) {
-            foreach ($server->getSystemDispatchMap() as $key => $val) {
-                $outAr[] = new Value($key, 'string');
-            }
+        foreach ($server->getSystemDispatchMap() as $key => $val) {
+            $outAr[] = new Value($key, 'string');
         }
 
         return new Response(new Value($outAr, 'array'));
     }
 
+    /**
+     * @param Server $server
+     * @param Request $req
+     * @return Response
+     */
     public static function _xmlrpcs_methodSignature($server, $req)
     {
         // let accept as parameter both an xmlrpc value or string
@@ -855,7 +957,7 @@ class Server
         } else {
             $methName = $req;
         }
-        if (strpos($methName, "system.") === 0) {
+        if ($server->isSyscall($methName)) {
             $dmap = $server->getSystemDispatchMap();
         } else {
             $dmap = $server->dmap;
@@ -883,6 +985,11 @@ class Server
         return $r;
     }
 
+    /**
+     * @param Server $server
+     * @param Request $req
+     * @return Response
+     */
     public static function _xmlrpcs_methodHelp($server, $req)
     {
         // let accept as parameter both an xmlrpc value or string
@@ -892,14 +999,14 @@ class Server
         } else {
             $methName = $req;
         }
-        if (strpos($methName, "system.") === 0) {
+        if ($server->isSyscall($methName)) {
             $dmap = $server->getSystemDispatchMap();
         } else {
             $dmap = $server->dmap;
         }
         if (isset($dmap[$methName])) {
             if (isset($dmap[$methName]['docstring'])) {
-                $r = new Response(new Value($dmap[$methName]['docstring']), 'string');
+                $r = new Response(new Value($dmap[$methName]['docstring'], 'string'));
             } else {
                 $r = new Response(new Value('', 'string'));
             }
@@ -926,6 +1033,11 @@ class Server
         return new Value($struct, 'struct');
     }
 
+    /**
+     * @param Server $server
+     * @param Value $call
+     * @return Value
+     */
     public static function _xmlrpcs_multicall_do_call($server, $call)
     {
         if ($call->kindOf() != 'struct') {
@@ -969,6 +1081,11 @@ class Server
         return new Value(array($result->value()), 'array');
     }
 
+    /**
+     * @param Server $server
+     * @param Value $call
+     * @return Value
+     */
     public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
     {
         if (!is_array($call)) {
@@ -990,13 +1107,17 @@ class Server
             return static::_xmlrpcs_multicall_error('notarray');
         }
 
-        // this is a real dirty and simplistic hack, since we might have received a
+        // this is a simplistic hack, since we might have received
         // base64 or datetime values, but they will be listed as strings here...
-        $numParams = count($call['params']);
         $pt = array();
         $wrapper = new Wrapper();
         foreach ($call['params'] as $val) {
-            $pt[] = $wrapper->php2XmlrpcType(gettype($val));
+            // support EPI-encoded base64 and datetime values
+            if ($val instanceof \stdClass && isset($val->xmlrpc_type)) {
+                $pt[] = $val->xmlrpc_type == 'datetime' ? Value::$xmlrpcDateTime : $val->xmlrpc_type;
+            } else {
+                $pt[] = $wrapper->php2XmlrpcType(gettype($val));
+            }
         }
 
         $result = $server->execute($call['methodName'], $call['params'], $pt);
@@ -1008,6 +1129,11 @@ class Server
         return new Value(array($result->value()), 'array');
     }
 
+    /**
+     * @param Server $server
+     * @param Request|array $req
+     * @return Response
+     */
     public static function _xmlrpcs_multicall($server, $req)
     {
         $result = array();
@@ -1052,7 +1178,10 @@ class Server
             // The previous error handler was the default: all we should do is log error
             // to the default error log (if level high enough)
             if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
-                error_log($errString);
+                if (self::$logger === null) {
+                    self::$logger = Logger::instance();
+                }
+                self::$logger->errorLog($errString);
             }
         } else {
             // Pass control on to previous error handler, trying to avoid loops...
index 97852b0..ec43fc1 100644 (file)
@@ -3,6 +3,7 @@
 namespace PhpXmlRpc;
 
 use PhpXmlRpc\Helper\Charset;
+use PhpXmlRpc\Helper\Logger;
 
 /**
  * This class enables the creation of values for XML-RPC, by encapsulating plain php values.
@@ -36,17 +37,52 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
         "null" => 1,
     );
 
+    protected static $logger;
+    protected static $charsetEncoder;
+
     /// @todo: do these need to be public?
+    /** @var Value[]|mixed */
     public $me = array();
+    /**
+     * @var int $mytype
+     * @internal
+     */
     public $mytype = 0;
+    /** @var string|null $_php_class */
     public $_php_class = null;
 
+    public function getLogger()
+    {
+        if (self::$logger === null) {
+            self::$logger = Logger::instance();
+        }
+        return self::$logger;
+    }
+
+    public static function setLogger($logger)
+    {
+        self::$logger = $logger;
+    }
+
+    public function getCharsetEncoder()
+    {
+        if (self::$charsetEncoder === null) {
+            self::$charsetEncoder = Charset::instance();
+        }
+        return self::$charsetEncoder;
+    }
+
+    public function setCharsetEncoder($charsetEncoder)
+    {
+        self::$charsetEncoder = $charsetEncoder;
+    }
+
     /**
      * Build an xmlrpc value.
      *
      * When no value or type is passed in, the value is left uninitialized, and the value can be added later.
      *
-     * @param mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
+     * @param Value[]|mixed $val if passing in an array, all array elements should be PhpXmlRpc\Value themselves
      * @param string $type any valid xmlrpc type name (lowercase): i4, int, boolean, string, double, dateTime.iso8601,
      *                     base64, array, struct, null.
      *                     If null, 'string' is assumed.
@@ -83,7 +119,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
                     $this->me['struct'] = $val;
                     break;
                 default:
-                    error_log("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
+                    $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": not a known type ($type)");
             }
         }
     }
@@ -108,7 +144,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
         }
 
         if ($typeOf !== 1) {
-            error_log("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
+            $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": not a scalar type ($type)");
             return 0;
         }
 
@@ -125,10 +161,10 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
 
         switch ($this->mytype) {
             case 1:
-                error_log('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': scalar xmlrpc value can have only one value');
                 return 0;
             case 3:
-                error_log('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot add anonymous scalar to struct xmlrpc value');
                 return 0;
             case 2:
                 // we're adding a scalar value to an array here
@@ -170,7 +206,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
 
             return 1;
         } else {
-            error_log('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
             return 0;
         }
     }
@@ -201,7 +237,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
 
             return 1;
         } else {
-            error_log('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': already initialized as a [' . $this->kindOf() . ']');
             return 0;
         }
     }
@@ -216,18 +252,21 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
         switch ($this->mytype) {
             case 3:
                 return 'struct';
-                break;
             case 2:
                 return 'array';
-                break;
             case 1:
                 return 'scalar';
-                break;
             default:
                 return 'undef';
         }
     }
 
+    /**
+     * @param string typ
+     * @param Value[]|mixed $val
+     * @param string $charsetEncoding
+     * @return string
+     */
     protected function serializedata($typ, $val, $charsetEncoding = '')
     {
         $rs = '';
@@ -246,9 +285,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
                         $rs .= "<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
                         break;
                     case static::$xmlrpcString:
-                        // G. Giunta 2005/2/13: do NOT use htmlentities, since
-                        // it will produce named html entities, which are invalid xml
-                        $rs .= "<${typ}>" . Charset::instance()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
+                        // Do NOT use htmlentities, since it will produce named html entities, which are invalid xml
+                        $rs .= "<${typ}>" . $this->getCharsetEncoder()->encodeEntities($val, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</${typ}>";
                         break;
                     case static::$xmlrpcInt:
                     case static::$xmlrpcI4:
@@ -261,7 +299,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
                         // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
                         // The code below tries its best at keeping max precision while avoiding exp notation,
                         // but there is of course no limit in the number of decimal places to be used...
-                        $rs .= "<${typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, 128, '.', '')) . "</${typ}>";
+                        $rs .= "<${typ}>" . preg_replace('/\\.?0+$/', '', number_format((double)$val, PhpXmlRpc::$xmlpc_double_precision, '.', '')) . "</${typ}>";
                         break;
                     case static::$xmlrpcDateTime:
                         if (is_string($val)) {
@@ -295,7 +333,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
                 } else {
                     $rs .= "<struct>\n";
                 }
-                $charsetEncoder = Charset::instance();
+                $charsetEncoder = $this->getCharsetEncoder();
+                /** @var Value $val2 */
                 foreach ($val as $key2 => $val2) {
                     $rs .= '<member><name>' . $charsetEncoder->encodeEntities($key2, PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "</name>\n";
                     //$rs.=$this->serializeval($val2);
@@ -307,6 +346,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
             case 2:
                 // array
                 $rs .= "<array>\n<data>\n";
+                /** @var Value $element */
                 foreach ($val as $element) {
                     //$rs.=$this->serializeval($val[$i]);
                     $rs .= $element->serialize($charsetEncoding);
@@ -329,8 +369,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function serialize($charsetEncoding = '')
     {
-        reset($this->me);
-        list($typ, $val) = each($this->me);
+        $val = reset($this->me);
+        $typ = key($this->me);
 
         return '<value>' . $this->serializedata($typ, $val, $charsetEncoding) . "</value>\n";
     }
@@ -348,6 +388,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function structmemexists($key)
     {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
         return array_key_exists($key, $this->me['struct']);
     }
 
@@ -363,6 +405,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function structmem($key)
     {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
         return $this->me['struct'][$key];
     }
 
@@ -372,6 +416,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function structreset()
     {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
         reset($this->me['struct']);
     }
 
@@ -379,12 +425,15 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      * Return next member element for xmlrpc values of type struct.
      *
      * @return Value
+     * @throws \Error starting with php 8.0, this function should not be used, as it will always throw
      *
      * @deprecated iterate directly over the object using foreach instead
      */
     public function structeach()
     {
-        return each($this->me['struct']);
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
+        return @each($this->me['struct']);
     }
 
     /**
@@ -394,8 +443,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function scalarval()
     {
-        reset($this->me);
-        list(, $b) = each($this->me);
+        $b = reset($this->me);
 
         return $b;
     }
@@ -410,7 +458,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
     public function scalartyp()
     {
         reset($this->me);
-        list($a,) = each($this->me);
+        $a = key($this->me);
         if ($a == static::$xmlrpcI4) {
             $a = static::$xmlrpcInt;
         }
@@ -429,6 +477,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function arraymem($key)
     {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
         return $this->me['array'][$key];
     }
 
@@ -441,6 +491,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function arraysize()
     {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
         return count($this->me['array']);
     }
 
@@ -453,6 +505,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      */
     public function structsize()
     {
+        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
+
         return count($this->me['struct']);
     }
 
@@ -464,6 +518,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
      *
      * @return integer
      */
+    #[\ReturnTypeWillChange]
     public function count()
     {
         switch ($this->mytype) {
@@ -481,9 +536,12 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
     /**
      * Implements the IteratorAggregate interface
      *
-     * @return ArrayIterator
+     * @return \ArrayIterator
+     * @internal required to be public to implement an Interface
      */
-    public function getIterator() {
+    #[\ReturnTypeWillChange]
+    public function getIterator()
+    {
         switch ($this->mytype) {
             case 3:
                 return new \ArrayIterator($this->me['struct']);
@@ -494,11 +552,17 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
             default:
                 return new \ArrayIterator();
         }
-        return new \ArrayIterator();
     }
 
-    public function offsetSet($offset, $value) {
-
+    /**
+     * @internal required to be public to implement an Interface
+     * @param mixed $offset
+     * @param mixed $value
+     * @throws \Exception
+     */
+    #[\ReturnTypeWillChange]
+    public function offsetSet($offset, $value)
+    {
         switch ($this->mytype) {
             case 3:
                 if (!($value instanceof \PhpXmlRpc\Value)) {
@@ -525,7 +589,7 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
             case 1:
 // todo: handle i4 vs int
                 reset($this->me);
-                list($type,) = each($this->me);
+                $type = key($this->me);
                 if ($type != $offset) {
                     throw new \Exception('');
                 }
@@ -537,7 +601,14 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
         }
     }
 
-    public function offsetExists($offset) {
+    /**
+     * @internal required to be public to implement an Interface
+     * @param mixed $offset
+     * @return bool
+     */
+    #[\ReturnTypeWillChange]
+    public function offsetExists($offset)
+    {
         switch ($this->mytype) {
             case 3:
                 return isset($this->me['struct'][$offset]);
@@ -551,7 +622,14 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
         }
     }
 
-    public function offsetUnset($offset) {
+    /**
+     * @internal required to be public to implement an Interface
+     * @param mixed $offset
+     * @throws \Exception
+     */
+    #[\ReturnTypeWillChange]
+    public function offsetUnset($offset)
+    {
         switch ($this->mytype) {
             case 3:
                 unset($this->me['struct'][$offset]);
@@ -567,7 +645,15 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
         }
     }
 
-    public function offsetGet($offset) {
+    /**
+     * @internal required to be public to implement an Interface
+     * @param mixed $offset
+     * @return mixed|Value|null
+     * @throws \Exception
+     */
+    #[\ReturnTypeWillChange]
+    public function offsetGet($offset)
+    {
         switch ($this->mytype) {
             case 3:
                 return isset($this->me['struct'][$offset]) ? $this->me['struct'][$offset] : null;
@@ -575,8 +661,8 @@ class Value implements \Countable, \IteratorAggregate, \ArrayAccess
                 return isset($this->me['array'][$offset]) ? $this->me['array'][$offset] : null;
             case 1:
 // on bad type: null or exception?
-                reset($this->me);
-                list($type, $value) = each($this->me);
+                $value = reset($this->me);
+                $type = key($this->me);
                 return $type == $offset ? $value : null;
             default:
 // return null or exception?
index ce12d9a..e000a06 100644 (file)
@@ -1,12 +1,14 @@
 <?php
 /**
  * @author Gaetano Giunta
- * @copyright (C) 2006-2015 G. Giunta
+ * @copyright (C) 2006-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  */
 
 namespace PhpXmlRpc;
 
+use PhpXmlRpc\Helper\Logger;
+
 /**
  * PHP-XMLRPC "wrapper" class - generate stubs to transparently access xmlrpc methods as php functions and vice-versa.
  * Note: this class implements the PROXY pattern, but it is not named so to avoid confusion with http proxies.
@@ -14,24 +16,43 @@ namespace PhpXmlRpc;
  * @todo use some better templating system for code generation?
  * @todo implement method wrapping with preservation of php objs in calls
  * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster)
+ * @todo add support for 'epivals' mode
+ * @todo allow setting custom namespace for generated wrapping code
  */
 class Wrapper
 {
     /// used to hold a reference to object instances whose methods get wrapped by wrapPhpFunction(), in 'create source' mode
     public static $objHolder = array();
 
+    protected static $logger;
+
+    public function getLogger()
+    {
+        if (self::$logger === null) {
+            self::$logger = Logger::instance();
+        }
+        return self::$logger;
+    }
+
+    public static function setLogger($logger)
+    {
+        self::$logger = $logger;
+    }
+
     /**
      * Given a string defining a php type or phpxmlrpc type (loosely defined: strings
      * accepted come from javadoc blocks), return corresponding phpxmlrpc type.
      * Notes:
      * - for php 'resource' types returns empty string, since resources cannot be serialized;
      * - for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
-     * - for php arrays always return array, even though arrays sometimes serialize as json structs
+     * - for php arrays always return array, even though arrays sometimes serialize as structs...
      * - for 'void' and 'null' returns 'undefined'
      *
      * @param string $phpType
      *
      * @return string
+     *
+     * @todo support notation `something[]` as 'array'
      */
     public function php2XmlrpcType($phpType)
     {
@@ -51,6 +72,7 @@ class Wrapper
             case 'true':
                 return Value::$xmlrpcBoolean;
             case Value::$xmlrpcArray: // 'array':
+            case 'array[]';
                 return Value::$xmlrpcArray;
             case 'object':
             case Value::$xmlrpcStruct: // 'struct'
@@ -61,6 +83,9 @@ class Wrapper
                 return '';
             default:
                 if (class_exists($phpType)) {
+                    if (is_a($phpType, 'DateTimeInterface')) {
+                        return Value::$xmlrpcDateTime;
+                    }
                     return Value::$xmlrpcStruct;
                 } else {
                     // unknown: might be any 'extended' xmlrpc type
@@ -110,7 +135,7 @@ class Wrapper
      * Since php is a typeless language, to infer types of input and output parameters,
      * it relies on parsing the javadoc-style comment block associated with the given
      * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
-     * in the @param tag is also allowed, if you need the php function to receive/send
+     * in the '@param' tag is also allowed, if you need the php function to receive/send
      * data in that particular format (note that base64 encoding/decoding is transparently
      * carried out by the lib, while datetime vals are passed around as strings)
      *
@@ -158,7 +183,7 @@ class Wrapper
         }
         if (is_array($callable)) {
             if (count($callable) < 2 || (!is_string($callable[0]) && !is_object($callable[0]))) {
-                error_log('XML-RPC: ' . __METHOD__ . ': syntax for function to be wrapped is wrong');
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': syntax for function to be wrapped is wrong');
                 return false;
             }
             if (is_string($callable[0])) {
@@ -170,7 +195,7 @@ class Wrapper
         } else if ($callable instanceof \Closure) {
             // we do not support creating code which wraps closures, as php does not allow to serialize them
             if (!$buildIt) {
-                error_log('XML-RPC: ' . __METHOD__ . ': a closure can not be wrapped in generated source code');
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': a closure can not be wrapped in generated source code');
                 return false;
             }
 
@@ -182,7 +207,7 @@ class Wrapper
         }
 
         if (!$exists) {
-            error_log('XML-RPC: ' . __METHOD__ . ': function to be wrapped is not defined: ' . $plainFuncName);
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': function to be wrapped is not defined: ' . $plainFuncName);
             return false;
         }
 
@@ -226,23 +251,23 @@ class Wrapper
         if (is_array($callable)) {
             $func = new \ReflectionMethod($callable[0], $callable[1]);
             if ($func->isPrivate()) {
-                error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is private: ' . $plainFuncName);
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is private: ' . $plainFuncName);
                 return false;
             }
             if ($func->isProtected()) {
-                error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is protected: ' . $plainFuncName);
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is protected: ' . $plainFuncName);
                 return false;
             }
             if ($func->isConstructor()) {
-                error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the constructor: ' . $plainFuncName);
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the constructor: ' . $plainFuncName);
                 return false;
             }
             if ($func->isDestructor()) {
-                error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the destructor: ' . $plainFuncName);
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is the destructor: ' . $plainFuncName);
                 return false;
             }
             if ($func->isAbstract()) {
-                error_log('XML-RPC: ' . __METHOD__ . ': method to be wrapped is abstract: ' . $plainFuncName);
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': method to be wrapped is abstract: ' . $plainFuncName);
                 return false;
             }
             /// @todo add more checks for static vs. nonstatic?
@@ -252,7 +277,7 @@ class Wrapper
         if ($func->isInternal()) {
             // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
             // instead of getparameters to fully reflect internal php functions ?
-            error_log('XML-RPC: ' . __METHOD__ . ': function to be wrapped is internal: ' . $plainFuncName);
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': function to be wrapped is internal: ' . $plainFuncName);
             return false;
         }
 
@@ -396,11 +421,15 @@ class Wrapper
      * @param $callable
      * @param array $extraOptions
      * @param string $plainFuncName
-     * @param string $funcDesc
+     * @param array $funcDesc
      * @return \Closure
      */
     protected function buildWrapFunctionClosure($callable, $extraOptions, $plainFuncName, $funcDesc)
     {
+        /**
+         * @param Request $req
+         * @return mixed
+         */
         $function = function($req) use($callable, $extraOptions, $funcDesc)
         {
             $nameSpace = '\\PhpXmlRpc\\';
@@ -552,7 +581,7 @@ class Wrapper
         }
 
         // since we are building source code for later use, if we are given an object instance,
-        // we go out of our way and store a pointer to it in a static class var var...
+        // we go out of our way and store a pointer to it in a static class var...
         if (is_array($callable) && is_object($callable[0])) {
             self::$objHolder[$newFuncName] = $callable[0];
             $innerCode .= "\$obj = PhpXmlRpc\\Wrapper::\$objHolder['$newFuncName'];\n";
@@ -593,15 +622,14 @@ class Wrapper
      * @param array $extraOptions see the docs for wrapPhpMethod for basic options, plus
      *                            - string method_type    'static', 'nonstatic', 'all' and 'auto' (default); the latter will switch between static and non-static depending on whether $className is a class name or object instance
      *                            - string method_filter  a regexp used to filter methods to wrap based on their names
-     *                            - string prefix         used for the names of the xmlrpc methods created
-     *
+     *                            - string prefix         used for the names of the xmlrpc methods created.
+     *                            - string replace_class_name use to completely replace the class name with the prefix in the generated method names. e.g. instead of \Some\Namespace\Class.method use prefixmethod
      * @return array|false false on failure
      */
     public function wrapPhpClass($className, $extraOptions = array())
     {
         $methodFilter = isset($extraOptions['method_filter']) ? $extraOptions['method_filter'] : '';
         $methodType = isset($extraOptions['method_type']) ? $extraOptions['method_type'] : 'auto';
-        $prefix = isset($extraOptions['prefix']) ? $extraOptions['prefix'] : '';
 
         $results = array();
         $mList = get_class_methods($className);
@@ -613,13 +641,9 @@ class Wrapper
                         (!$func->isStatic() && ($methodType == 'all' || $methodType == 'nonstatic' || ($methodType == 'auto' && is_object($className))))
                     ) {
                         $methodWrap = $this->wrapPhpFunction(array($className, $mName), '', $extraOptions);
+
                         if ($methodWrap) {
-                            if (is_object($className)) {
-                                $realClassName = get_class($className);
-                            }else {
-                                $realClassName = $className;
-                            }
-                            $results[$prefix."$realClassName.$mName"] = $methodWrap;
+                            $results[$this->generateMethodNameForClassMethod($className, $mName, $extraOptions)] = $methodWrap;
                         }
                     }
                 }
@@ -629,6 +653,26 @@ class Wrapper
         return $results;
     }
 
+    /**
+     * @param string|object $className
+     * @param string $classMethod
+     * @param array $extraOptions
+     * @return string
+     */
+    protected function generateMethodNameForClassMethod($className, $classMethod, $extraOptions = array())
+    {
+        if (isset($extraOptions['replace_class_name']) && $extraOptions['replace_class_name']) {
+            return (isset($extraOptions['prefix']) ?  $extraOptions['prefix'] : '') . $classMethod;
+        }
+
+        if (is_object($className)) {
+            $realClassName = get_class($className);
+        } else {
+            $realClassName = $className;
+        }
+        return (isset($extraOptions['prefix']) ?  $extraOptions['prefix'] : '') . "$realClassName.$classMethod";
+    }
+
     /**
      * Given an xmlrpc client and a method name, register a php wrapper function
      * that will call it and return results using native php types for both
@@ -672,7 +716,7 @@ class Wrapper
      *                            - bool    debug               set it to 1 or 2 to see debug results of querying server for method synopsis
      *                            - int     simple_client_copy  set it to 1 to have a lightweight copy of the $client object made in the generated code (only used when return_source = true)
      *
-     * @return \closure|array|false false on failure, closure by default and array for return_source = true
+     * @return \closure|string[]|false false on failure, closure by default and array for return_source = true
      */
     public function wrapXmlrpcMethod($client, $methodName, $extraOptions = array())
     {
@@ -704,7 +748,6 @@ class Wrapper
 
             return $results;
         }
-
     }
 
     /**
@@ -731,7 +774,7 @@ class Wrapper
         $client->setDebug($debug);
         $response = $client->send($req, $timeout, $protocol);
         if ($response->faultCode()) {
-            error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature from remote server for method ' . $methodName);
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature from remote server for method ' . $methodName);
             return false;
         }
 
@@ -742,7 +785,7 @@ class Wrapper
         }
 
         if (!is_array($mSig) || count($mSig) <= $sigNum) {
-            error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature nr.' . $sigNum . ' from remote server for method ' . $methodName);
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve method signature nr.' . $sigNum . ' from remote server for method ' . $methodName);
             return false;
         }
 
@@ -785,7 +828,7 @@ class Wrapper
      * @param Client $client
      * @param string $methodName
      * @param array $extraOptions
-     * @param string $mSig
+     * @param array $mSig
      * @return \Closure
      *
      * @todo should we allow usage of parameter simple_client_copy to mean 'do not clone' in this case?
@@ -879,7 +922,7 @@ class Wrapper
      * @param string $newFuncName
      * @param array $mSig
      * @param string $mDesc
-     * @return array
+     * @return string[] keys: source, docstring
      */
     public function buildWrapMethodSource($client, $methodName, array $extraOptions, $newFuncName, $mSig, $mDesc='')
     {
@@ -947,7 +990,7 @@ class Wrapper
             $mDesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n";
         }
         $plist = implode(', ', $plist);
-        $mDesc .= '* @return ' . $this->xmlrpc2PhpType($mSig[0]) . " (or an {$namespace}Response obj instance if call fails)\n*/\n";
+        $mDesc .= '* @return {$namespace}Response|' . $this->xmlrpc2PhpType($mSig[0]) . " (an {$namespace}Response obj instance if call fails)\n*/\n";
 
         $innerCode .= "\$res = \${$this_}client->send(\$req, $timeout, '$protocol');\n";
         if ($decodeFault) {
@@ -986,7 +1029,7 @@ class Wrapper
      *              - string prefix
      *              - bool   simple_client_copy set it to true to avoid copying all properties of $client into the copy made in the new class
      *
-     * @return mixed false on error, the name of the created class if all ok or an array with code, class name and comments (if the appropriatevoption is set in extra_options)
+     * @return mixed false on error, the name of the created class if all ok or an array with code, class name and comments (if the appropriate option is set in extra_options)
      */
     public function wrapXmlrpcServer($client, $extraOptions = array())
     {
@@ -1007,7 +1050,7 @@ class Wrapper
         $req = new $reqClass('system.listMethods');
         $response = $client->send($req, $timeout, $protocol);
         if ($response->faultCode()) {
-            error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve method list from remote server');
+            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve method list from remote server');
 
             return false;
         } else {
@@ -1017,7 +1060,7 @@ class Wrapper
                 $mList = $decoder->decode($mList);
             }
             if (!is_array($mList) || !count($mList)) {
-                error_log('XML-RPC: ' . __METHOD__ . ': could not retrieve meaningful method list from remote server');
+                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not retrieve meaningful method list from remote server');
 
                 return false;
             } else {
@@ -1059,7 +1102,7 @@ class Wrapper
                             }
                             $source .= $methodWrap['source'] . "\n";
                         } else {
-                            error_log('XML-RPC: ' . __METHOD__ . ': will not create class method to wrap remote method ' . $mName);
+                            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': will not create class method to wrap remote method ' . $mName);
                         }
                     }
                 }
@@ -1070,7 +1113,7 @@ class Wrapper
                     if ($allOK) {
                         return $xmlrpcClassName;
                     } else {
-                        error_log('XML-RPC: ' . __METHOD__ . ': could not create class ' . $xmlrpcClassName . ' to wrap remote server ' . $client->server);
+                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': could not create class ' . $xmlrpcClassName . ' to wrap remote server ' . $client->server);
                         return false;
                     }
                 } else {
@@ -1100,7 +1143,12 @@ class Wrapper
         // (this provides for future expansion or subclassing of client obj)
         if ($verbatimClientCopy) {
             foreach ($client as $fld => $val) {
-                if ($fld != 'debug' && $fld != 'return_type') {
+                /// @todo in php 8.0, curl handles became objects, but they have no __set_state, thus var_export will
+                ///        fail for xmlrpc_curl_handle. So we disabled copying it.
+                ///        We should examine in depth if this change can have side effects - at first sight if the
+                ///        client's curl handle is not set, all curl options are (re)set on each http call, so there
+                ///        should be no loss of state...
+                if ($fld != 'debug' && $fld != 'return_type' && $fld != 'xmlrpc_curl_handle') {
                     $val = var_export($val, true);
                     $code .= "\$client->$fld = $val;\n";
                 }
index 8a62506..a578911 100644 (file)
@@ -3,6 +3,8 @@
  * @author JoakimLofgren
  */
 
+include_once __DIR__ . '/PolyfillTestCase.php';
+
 use PhpXmlRpc\Helper\Charset;
 
 /**
@@ -12,17 +14,23 @@ use PhpXmlRpc\Helper\Charset;
  * and run the following in cmd:
  *     chcp 28591 (latin1)
  *     chcp 65001 (utf8)
+ *
+ * @todo add tests for conversion: utf8 -> ascii (incl. chars 0-31 and 127)
+ * @todo add tests for conversion: latin1 -> utf8
+ * @todo add tests for conversion: latin1 -> ascii (incl. chars 0-31 and 127)
  */
-class CharsetTest extends PHPUnit_Framework_TestCase
+class CharsetTest extends PhpXmlRpc_PolyfillTestCase
 {
     // Consolas font should render these properly
     protected $runes = "ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ";
     protected $greek = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ";
     protected $russian = "Река неслася; бедный чёлн";
     protected $chinese = "我能吞下玻璃而不伤身体。";
+
     protected $latinString;
 
-    protected function setUp()
+    /// @todo move to usage of a dataProvider and create the latinString there
+    protected function set_up()
     {
         // construct a latin string with all chars (except control ones)
         $this->latinString = "\n\r\t";
@@ -34,7 +42,7 @@ class CharsetTest extends PHPUnit_Framework_TestCase
         }
     }
 
-    protected function utfToLatin($data)
+    protected function utf8ToLatin1($data)
     {
         return Charset::instance()->encodeEntities(
             $data,
@@ -43,6 +51,15 @@ class CharsetTest extends PHPUnit_Framework_TestCase
         );
     }
 
+    protected function utf8ToAscii($data)
+    {
+        return Charset::instance()->encodeEntities(
+            $data,
+            'UTF-8',
+            'US-ASCII'
+        );
+    }
+
     public function testUtf8ToLatin1All()
     {
         /*$this->assertEquals(
@@ -51,42 +68,42 @@ class CharsetTest extends PHPUnit_Framework_TestCase
             'Setup latinString is not ISO-8859-1 encoded...'
         );*/
         $string = utf8_encode($this->latinString);
-        $encoded = $this->utfToLatin($string);
+        $encoded = $this->utf8ToLatin1($string);
         $this->assertEquals(str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $this->latinString), $encoded);
     }
 
     public function testUtf8ToLatin1EuroSymbol()
     {
         $string = 'a.b.c.å.ä.ö.€.';
-        $encoded = $this->utfToLatin($string);
+        $encoded = $this->utf8ToLatin1($string);
         $this->assertEquals(utf8_decode('a.b.c.å.ä.ö.&#8364;.'), $encoded);
     }
 
     public function testUtf8ToLatin1Runes()
     {
         $string = $this->runes;
-        $encoded = $this->utfToLatin($string);
+        $encoded = $this->utf8ToLatin1($string);
         $this->assertEquals('&#5792;&#5831;&#5819;&#5867;&#5842;&#5862;&#5798;&#5867;&#5792;&#5809;&#5801;&#5792;&#5794;&#5809;&#5867;&#5792;&#5825;&#5809;&#5802;&#5867;&#5815;&#5846;&#5819;&#5817;&#5862;&#5850;&#5811;&#5794;&#5847;', $encoded);
     }
 
     public function testUtf8ToLatin1Greek()
     {
         $string = $this->greek;
-        $encoded = $this->utfToLatin($string);
+        $encoded = $this->utf8ToLatin1($string);
         $this->assertEquals('&#932;&#8052; &#947;&#955;&#8182;&#963;&#963;&#945; &#956;&#959;&#8166; &#7956;&#948;&#969;&#963;&#945;&#957; &#7953;&#955;&#955;&#951;&#957;&#953;&#954;&#8052;', $encoded);
     }
 
     public function testUtf8ToLatin1Russian()
     {
         $string = $this->russian;
-        $encoded = $this->utfToLatin($string);
+        $encoded = $this->utf8ToLatin1($string);
         $this->assertEquals('&#1056;&#1077;&#1082;&#1072; &#1085;&#1077;&#1089;&#1083;&#1072;&#1089;&#1103;; &#1073;&#1077;&#1076;&#1085;&#1099;&#1081; &#1095;&#1105;&#1083;&#1085;', $encoded);
     }
 
     public function testUtf8ToLatin1Chinese()
     {
         $string = $this->chinese;
-        $encoded = $this->utfToLatin($string);
+        $encoded = $this->utf8ToLatin1($string);
         $this->assertEquals('&#25105;&#33021;&#21534;&#19979;&#29627;&#29827;&#32780;&#19981;&#20260;&#36523;&#20307;&#12290;', $encoded);
     }
 }
diff --git a/php/phpxmlrpc/tests/1ValueTest.php b/php/phpxmlrpc/tests/1ValueTest.php
new file mode 100644 (file)
index 0000000..c77432e
--- /dev/null
@@ -0,0 +1,173 @@
+<?php
+
+include_once __DIR__ . '/../lib/xmlrpc.inc';
+include_once __DIR__ . '/../lib/xmlrpcs.inc';
+
+include_once __DIR__ . '/parse_args.php';
+
+include_once __DIR__ . '/PolyfillTestCase.php';
+
+use PHPUnit\Runner\BaseTestRunner;
+
+/**
+ * Tests involving the Value class
+ */
+class ValueTests extends PhpXmlRpc_PolyfillTestCase
+{
+    public $args = array();
+
+    protected function set_up()
+    {
+        $this->args = argParser::getArgs();
+        if ($this->args['DEBUG'] == 1)
+            ob_start();
+    }
+
+    protected function tear_down()
+    {
+        if ($this->args['DEBUG'] != 1)
+            return;
+        $out = ob_get_clean();
+        $status = $this->getStatus();
+        if ($status == BaseTestRunner::STATUS_ERROR
+            || $status == BaseTestRunner::STATUS_FAILURE) {
+            echo $out;
+        }
+    }
+
+    public function testMinusOneString()
+    {
+        $v = new xmlrpcval('-1');
+        $u = new xmlrpcval('-1', 'string');
+        $t = new xmlrpcval(-1, 'string');
+        $this->assertEquals($v->scalarval(), $u->scalarval());
+        $this->assertEquals($v->scalarval(), $t->scalarval());
+    }
+
+    /**
+     * This looks funny, and we might call it a bug. But we strive for 100 backwards compat...
+     */
+    public function testMinusOneInt()
+    {
+        $u = new xmlrpcval();
+        $v = new xmlrpcval(-1);
+        $this->assertEquals($u->scalarval(), $v->scalarval());
+    }
+
+    public function testAddScalarToStruct()
+    {
+        $v = new xmlrpcval(array('a' => 'b'), 'struct');
+        // use @ operator in case error_log gets on screen
+        $r = @$v->addscalar('c');
+        $this->assertEquals(0, $r);
+    }
+
+    public function testAddStructToStruct()
+    {
+        $v = new xmlrpcval(array('a' => new xmlrpcval('b')), 'struct');
+        $r = $v->addstruct(array('b' => new xmlrpcval('c')));
+        $this->assertEquals(2, $v->structsize());
+        $this->assertEquals(1, $r);
+        $r = $v->addstruct(array('b' => new xmlrpcval('b')));
+        $this->assertEquals(2, $v->structsize());
+    }
+
+    public function testAddArrayToArray()
+    {
+        $v = new xmlrpcval(array(new xmlrpcval('a'), new xmlrpcval('b')), 'array');
+        $r = $v->addarray(array(new xmlrpcval('b'), new xmlrpcval('c')));
+        $this->assertEquals(4, $v->arraysize());
+        $this->assertEquals(1, $r);
+    }
+
+    public function testUTF8IntString()
+    {
+        $v = new xmlrpcval(100, 'int');
+        $s = $v->serialize('UTF-8');
+        $this->assertequals("<value><int>100</int></value>\n", $s);
+    }
+
+    public function testStringInt()
+    {
+        $v = new xmlrpcval('hello world', 'int');
+        $s = $v->serialize();
+        $this->assertequals("<value><int>0</int></value>\n", $s);
+    }
+
+    public function testStructMemExists()
+    {
+        $v = new xmlrpcval(array('hello' => new xmlrpcval('world')), 'struct');
+        $b = $v->structmemexists('hello');
+        $this->assertequals(true, $b);
+        $b = $v->structmemexists('world');
+        $this->assertequals(false, $b);
+    }
+
+    public function testLocale()
+    {
+        $locale = setlocale(LC_NUMERIC, 0);
+        /// @todo on php 5.3/win setting locale to german does not seem to set decimal separator to comma...
+        if (setlocale(LC_NUMERIC, 'deu', 'de_DE@euro', 'de_DE', 'de', 'ge') !== false) {
+            $v = new xmlrpcval(1.1, 'double');
+            if (strpos($v->scalarval(), ',') == 1) {
+                $r = $v->serialize();
+                $this->assertequals(false, strpos($r, ','));
+                setlocale(LC_NUMERIC, $locale);
+            } else {
+                setlocale(LC_NUMERIC, $locale);
+                $this->markTestSkipped('did not find a locale which sets decimal separator to comma');
+            }
+        } else {
+            $this->markTestSkipped('did not find a locale which sets decimal separator to comma');
+        }
+    }
+
+    public function testArrayAccess()
+    {
+        $v1 = new xmlrpcval(array(new xmlrpcval('one'), new xmlrpcval('two')), 'array');
+        $this->assertequals(1, count($v1));
+        $out = array('me' => array(), 'mytype' => 2, '_php_class' => null);
+
+        foreach($v1 as $key => $val)
+        {
+            $this->assertArrayHasKey($key, $out);
+            $expected = $out[$key];
+            if (gettype($expected) == 'array') {
+                $this->assertequals('array', gettype($val));
+            } else {
+                $this->assertequals($expected, $val);
+            }
+        }
+
+        $v2 = new \PhpXmlRpc\Value(array(new \PhpXmlRpc\Value('one'), new \PhpXmlRpc\Value('two')), 'array');
+        $this->assertequals(2, count($v2));
+        $out = array(array('key' => 0, 'value'  => 'object'), array('key' => 1, 'value'  => 'object'));
+        $i = 0;
+        foreach($v2 as $key => $val)
+        {
+            $expected = $out[$i];
+            $this->assertequals($expected['key'], $key);
+            $this->assertequals($expected['value'], gettype($val));
+            $i++;
+        }
+    }
+
+    /// @todo do not use \PhpXmlRpc\Encoder for this test
+    function testBigXML()
+    {
+        // nb: make sure that  the serialized xml corresponding to this is > 10MB in size
+        $data = array();
+        for ($i = 0; $i < 500000; $i++ ) {
+            $data[] = 'hello world';
+        }
+
+        $encoder = new \PhpXmlRpc\Encoder();
+        $val = $encoder->encode($data);
+        $req = new \PhpXmlRpc\Request('test', array($val));
+        $xml = $req->serialize();
+        $parser = new \PhpXmlRpc\Helper\XMLParser();
+        $parser->parse($xml);
+
+        $this->assertequals(0, $parser->_xh['isf']);
+    }
+}
similarity index 71%
rename from php/phpxmlrpc/tests/1ParsingBugsTest.php
rename to php/phpxmlrpc/tests/2MessageTest.php
index ce463f7..bb7461a 100644 (file)
@@ -1,34 +1,38 @@
 <?php
-/**
- * NB: do not let your IDE fool you. The correct encoding for this file is NOT UTF8.
- */
+
 include_once __DIR__ . '/../lib/xmlrpc.inc';
 include_once __DIR__ . '/../lib/xmlrpcs.inc';
 
 include_once __DIR__ . '/parse_args.php';
 
+include_once __DIR__ . '/PolyfillTestCase.php';
+
+use PHPUnit\Runner\BaseTestRunner;
+
 /**
- * Tests involving parsing of xml and handling of xmlrpc values
+ * Tests involving the Request and Response classes.
+ * @todo some tests are here only because we use a Response to trigger parsing of xml for a single Value, but they
+ *       logically belong elsewhere...
  */
-class ParsingBugsTests extends PHPUnit_Framework_TestCase
+class MessageTests extends PhpXmlRpc_PolyfillTestCase
 {
     public $args = array();
 
-    protected function setUp()
+    protected function set_up()
     {
         $this->args = argParser::getArgs();
         if ($this->args['DEBUG'] == 1)
             ob_start();
     }
 
-    protected function tearDown()
+    protected function tear_down()
     {
         if ($this->args['DEBUG'] != 1)
             return;
         $out = ob_get_clean();
         $status = $this->getStatus();
-        if ($status == PHPUnit_Runner_BaseTestRunner::STATUS_ERROR
-            || $status == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {
+        if ($status == BaseTestRunner::STATUS_ERROR
+            || $status == BaseTestRunner::STATUS_FAILURE) {
             echo $out;
         }
     }
@@ -40,66 +44,6 @@ class ParsingBugsTests extends PHPUnit_Framework_TestCase
         return $msg;
     }
 
-    public function testMinusOneString()
-    {
-        $v = new xmlrpcval('-1');
-        $u = new xmlrpcval('-1', 'string');
-        $t = new xmlrpcval(-1, 'string');
-        $this->assertEquals($v->scalarval(), $u->scalarval());
-        $this->assertEquals($v->scalarval(), $t->scalarval());
-    }
-
-    /**
-     * This looks funny, and we might call it a bug. But we strive for 100 backwards compat...
-     */
-    public function testMinusOneInt()
-    {
-        $u = new xmlrpcval();
-        $v = new xmlrpcval(-1);
-        $this->assertEquals($u->scalarval(), $v->scalarval());
-    }
-
-    public function testUnicodeInMemberName()
-    {
-        $str = "G" . chr(252) . "nter, El" . chr(232) . "ne";
-        $v = array($str => new xmlrpcval(1));
-        $r = new xmlrpcresp(new xmlrpcval($v, 'struct'));
-        $r = $r->serialize();
-        $m = $this->newMsg('dummy');
-        $r = $m->parseResponse($r);
-        $v = $r->value();
-        $this->assertEquals(true, $v->structmemexists($str));
-    }
-
-    public function testUnicodeInErrorString()
-    {
-        $response = utf8_encode(
-            '<?xml version="1.0"?>
-<!-- $Id -->
-<!-- found by G. giunta, covers what happens when lib receives
-  UTF8 chars in response text and comments -->
-<!-- ' . chr(224) . chr(252) . chr(232) . '&#224;&#252;&#232; -->
-<methodResponse>
-<fault>
-<value>
-<struct>
-<member>
-<name>faultCode</name>
-<value><int>888</int></value>
-</member>
-<member>
-<name>faultString</name>
-<value><string>' . chr(224) . chr(252) . chr(232) . '&#224;&#252;&#232;</string></value>
-</member>
-</struct>
-</value>
-</fault>
-</methodResponse>');
-        $m = $this->newMsg('dummy');
-        $r = $m->parseResponse($response);
-        $v = $r->faultString();
-        $this->assertEquals(chr(224) . chr(252) . chr(232) . chr(224) . chr(252) . chr(232), $v);
-    }
 
     public function testValidNumbers()
     {
@@ -160,7 +104,7 @@ class ParsingBugsTests extends PHPUnit_Framework_TestCase
     public function testI8()
     {
         if (PHP_INT_SIZE == 4 ) {
-            $this->markTestSkipped('did not find a locale which sets decimal separator to comma');
+            $this->markTestSkipped('Can not test i8 as php is compiled in 32 bit mode');
             return;
         }
 
@@ -187,43 +131,45 @@ class ParsingBugsTests extends PHPUnit_Framework_TestCase
         $this->assertEquals(1, $s->scalarval());
     }
 
-    public function testAddScalarToStruct()
-    {
-        $v = new xmlrpcval(array('a' => 'b'), 'struct');
-        // use @ operator in case error_log gets on screen
-        $r = @$v->addscalar('c');
-        $this->assertEquals(0, $r);
-    }
-
-    public function testAddStructToStruct()
-    {
-        $v = new xmlrpcval(array('a' => new xmlrpcval('b')), 'struct');
-        $r = $v->addstruct(array('b' => new xmlrpcval('c')));
-        $this->assertEquals(2, $v->structsize());
-        $this->assertEquals(1, $r);
-        $r = $v->addstruct(array('b' => new xmlrpcval('b')));
-        $this->assertEquals(2, $v->structsize());
-    }
-
-    public function testAddArrayToArray()
-    {
-        $v = new xmlrpcval(array(new xmlrpcval('a'), new xmlrpcval('b')), 'array');
-        $r = $v->addarray(array(new xmlrpcval('b'), new xmlrpcval('c')));
-        $this->assertEquals(4, $v->arraysize());
-        $this->assertEquals(1, $r);
-    }
-
-    public function testEncodeArray()
+    public function testUnicodeInMemberName()
     {
-        $r = range(1, 100);
-        $v = php_xmlrpc_encode($r);
-        $this->assertEquals('array', $v->kindof());
+        $str = "G" . chr(252) . "nter, El" . chr(232) . "ne";
+        $v = array($str => new xmlrpcval(1));
+        $r = new xmlrpcresp(new xmlrpcval($v, 'struct'));
+        $r = $r->serialize();
+        $m = $this->newMsg('dummy');
+        $r = $m->parseResponse($r);
+        $v = $r->value();
+        $this->assertEquals(true, $v->structmemexists($str));
     }
 
-    public function testEncodeRecursive()
+    public function testUnicodeInErrorString()
     {
-        $v = php_xmlrpc_encode(php_xmlrpc_encode('a simple string'));
-        $this->assertEquals('scalar', $v->kindof());
+        $response = utf8_encode(
+            '<?xml version="1.0"?>
+<!-- $Id -->
+<!-- found by G. Giunta, covers what happens when lib receives UTF8 chars in response text and comments -->
+<!-- ' . chr(224) . chr(252) . chr(232) . '&#224;&#252;&#232; -->
+<methodResponse>
+<fault>
+<value>
+<struct>
+<member>
+<name>faultCode</name>
+<value><int>888</int></value>
+</member>
+<member>
+<name>faultString</name>
+<value><string>' . chr(224) . chr(252) . chr(232) . '&#224;&#252;&#232;</string></value>
+</member>
+</struct>
+</value>
+</fault>
+</methodResponse>');
+        $m = $this->newMsg('dummy');
+        $r = $m->parseResponse($response);
+        $v = $r->faultString();
+        $this->assertEquals(chr(224) . chr(252) . chr(232) . chr(224) . chr(252) . chr(232), $v);
     }
 
     public function testBrokenRequests()
@@ -425,7 +371,7 @@ and there they were.</value></member><member><name>postid</name><value>7414222</
     {
         $s = $this->newMsg('dummy');
         $f = '<?xml version="1.0"?><methodResponse><params><param><value><struct><member><name>userid</name><value>311127</value></member>
-<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>hello world. 2 newlines follow
+<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>hello world. 3 newlines follow
 
 
 and there they were.</value></member><member><name>postid</name><value>7414222</value></member></struct></value></param></params></methodResponse>
@@ -433,14 +379,14 @@ and there they were.</value></member><member><name>postid</name><value>7414222</
         $r = $s->parseResponse($f, true, 'phpvals');
         $v = $r->value();
         $s = $v['content'];
-        $this->assertEquals("hello world. 2 newlines follow\n\n\nand there they were.", $s);
+        $this->assertEquals("hello world. 3 newlines follow\n\n\nand there they were.", $s);
     }
 
     public function testNoDecodeResponse()
     {
         $s = $this->newMsg('dummy');
         $f = '<?xml version="1.0"?><methodResponse><params><param><value><struct><member><name>userid</name><value>311127</value></member>
-<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>hello world. 2 newlines follow
+<member><name>dateCreated</name><value><dateTime.iso8601>20011126T09:17:52</dateTime.iso8601></value></member><member><name>content</name><value>hello world. 3 newlines follow
 
 
 and there they were.</value></member><member><name>postid</name><value>7414222</value></member></struct></value></param></params></methodResponse>';
@@ -449,25 +395,6 @@ and there they were.</value></member><member><name>postid</name><value>7414222</
         $this->assertEquals($f, $v);
     }
 
-    public function testAutoCoDec()
-    {
-        $data1 = array(1, 1.0, 'hello world', true, '20051021T23:43:00', -1, 11.0, '~!@#$%^&*()_+|', false, '20051021T23:43:00');
-        $data2 = array('zero' => $data1, 'one' => $data1, 'two' => $data1, 'three' => $data1, 'four' => $data1, 'five' => $data1, 'six' => $data1, 'seven' => $data1, 'eight' => $data1, 'nine' => $data1);
-        $data = array($data2, $data2, $data2, $data2, $data2, $data2, $data2, $data2, $data2, $data2);
-        //$keys = array('zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine');
-        $v1 = php_xmlrpc_encode($data, array('auto_dates'));
-        $v2 = php_xmlrpc_decode_xml($v1->serialize());
-        $this->assertEquals($v1, $v2);
-        $r1 = new PhpXmlRpc\Response($v1);
-        $r2 = php_xmlrpc_decode_xml($r1->serialize());
-        $r2->serialize(); // needed to set internal member payload
-        $this->assertEquals($r1, $r2);
-        $m1 = new PhpXmlRpc\Request('hello dolly', array($v1));
-        $m2 = php_xmlrpc_decode_xml($m1->serialize());
-        $m2->serialize(); // needed to set internal member payload
-        $this->assertEquals($m1, $m2);
-    }
-
     public function testUTF8Request()
     {
         $sendstring = 'κόσμε'; // Greek word 'kosme'. NB: NOT a valid ISO8859 string!
@@ -501,6 +428,7 @@ and there they were.</value></member><member><name>postid</name><value>7414222</
         $v = $v['content'];
         $this->assertEquals($string, $v);
 
+        /// @todo move to EncoderTest
         $r = php_xmlrpc_decode_xml($f);
         $v = $r->value();
         $v = $v->structmem('content')->scalarval();
@@ -528,35 +456,14 @@ and there they were.</value></member><member><name>postid</name><value>7414222</
         $v = $v['content'];
         $this->assertEquals($string, $v);
 
+        /// @todo move to EncoderTest
         $r = php_xmlrpc_decode_xml($f);
         $v = $r->value();
         $v = $v->structmem('content')->scalarval();
         $this->assertEquals($string, $v);
     }
 
-    public function testUTF8IntString()
-    {
-        $v = new xmlrpcval(100, 'int');
-        $s = $v->serialize('UTF-8');
-        $this->assertequals("<value><int>100</int></value>\n", $s);
-    }
-
-    public function testStringInt()
-    {
-        $v = new xmlrpcval('hello world', 'int');
-        $s = $v->serialize();
-        $this->assertequals("<value><int>0</int></value>\n", $s);
-    }
-
-    public function testStructMemExists()
-    {
-        $v = php_xmlrpc_encode(array('hello' => 'world'));
-        $b = $v->structmemexists('hello');
-        $this->assertequals(true, $b);
-        $b = $v->structmemexists('world');
-        $this->assertequals(false, $b);
-    }
-
+    /// @todo can we change this test to purely using the Value class ?
     public function testNilvalue()
     {
         // default case: we do not accept nil values received
@@ -590,50 +497,4 @@ and there they were.</value></member><member><name>postid</name><value>7414222</
         $r = $m->parseresponse($s);
         $this->assertequals(2, $r->faultCode());
     }
-
-    public function testLocale()
-    {
-        $locale = setlocale(LC_NUMERIC, 0);
-        /// @todo on php 5.3/win setting locale to german does not seem to set decimal separator to comma...
-        if (setlocale(LC_NUMERIC, 'deu', 'de_DE@euro', 'de_DE', 'de', 'ge') !== false) {
-            $v = new xmlrpcval(1.1, 'double');
-            if (strpos($v->scalarval(), ',') == 1) {
-                $r = $v->serialize();
-                $this->assertequals(false, strpos($r, ','));
-                setlocale(LC_NUMERIC, $locale);
-            } else {
-                setlocale(LC_NUMERIC, $locale);
-                $this->markTestSkipped('did not find a locale which sets decimal separator to comma');
-            }
-        } else {
-            $this->markTestSkipped('did not find a locale which sets decimal separator to comma');
-        }
-    }
-
-    public function testArrayAccess()
-    {
-        $v1 = new xmlrpcval(array(new xmlrpcval('one'), new xmlrpcval('two')), 'array');
-        $this->assertequals(1, count($v1));
-        $out = array('me' => array(), 'mytype' => 2, '_php_class' => null);
-        foreach($v1 as $key => $val)
-        {
-            $expected = each($out);
-            $this->assertequals($expected['key'], $key);
-            if (gettype($expected['value']) == 'array') {
-                $this->assertequals('array', gettype($val));
-            } else {
-                $this->assertequals($expected['value'], $val);
-            }
-        }
-
-        $v2 = new \PhpXmlRpc\Value(array(new \PhpXmlRpc\Value('one'), new \PhpXmlRpc\Value('two')), 'array');
-        $this->assertequals(2, count($v2));
-        $out = array(0 => 'object', 1 => 'object');
-        foreach($v2 as $key => $val)
-        {
-            $expected = each($out);
-            $this->assertequals($expected['key'], $key);
-            $this->assertequals($expected['value'], gettype($val));
-        }
-    }
 }
diff --git a/php/phpxmlrpc/tests/3EncoderTest.php b/php/phpxmlrpc/tests/3EncoderTest.php
new file mode 100644 (file)
index 0000000..2c41d75
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+
+include_once __DIR__ . '/../lib/xmlrpc.inc';
+include_once __DIR__ . '/../lib/xmlrpcs.inc';
+
+include_once __DIR__ . '/parse_args.php';
+
+include_once __DIR__ . '/PolyfillTestCase.php';
+
+use PHPUnit\Runner\BaseTestRunner;
+
+/**
+ * Tests involving automatic encoding/decoding of php values into xmlrpc values
+ * @todo add tests for encoding options: 'encode_php_objs', 'auto_dates', 'null_extension' and 'extension_api'
+ * @todo add tests for php_xmlrpc_decode options
+ */
+class EncoderTests extends PhpXmlRpc_PolyfillTestCase
+{
+    public $args = array();
+
+    protected function set_up()
+    {
+        $this->args = argParser::getArgs();
+        if ($this->args['DEBUG'] == 1)
+            ob_start();
+    }
+
+    protected function tear_down()
+    {
+        if ($this->args['DEBUG'] != 1)
+            return;
+        $out = ob_get_clean();
+        $status = $this->getStatus();
+        if ($status == BaseTestRunner::STATUS_ERROR
+            || $status == BaseTestRunner::STATUS_FAILURE) {
+            echo $out;
+        }
+    }
+
+    public function testEncodeArray()
+    {
+        $v = php_xmlrpc_encode(array());
+        $this->assertEquals('array', $v->kindof());
+
+        $r = range(1, 10);
+        $v = php_xmlrpc_encode($r);
+        $this->assertEquals('array', $v->kindof());
+
+        $r['.'] = '...';
+        $v = php_xmlrpc_encode($r);
+        $this->assertEquals('struct', $v->kindof());
+    }
+
+    public function testEncodeDate()
+    {
+        $r = new DateTime();
+        $v = php_xmlrpc_encode($r);
+        $this->assertEquals('dateTime.iso8601', $v->scalartyp());
+    }
+
+    public function testEncodeRecursive()
+    {
+        $v = php_xmlrpc_encode(php_xmlrpc_encode('a simple string'));
+        $this->assertEquals('scalar', $v->kindof());
+    }
+
+    public function testAutoCoDec()
+    {
+        $data1 = array(1, 1.0, 'hello world', true, '20051021T23:43:00', -1, 11.0, '~!@#$%^&*()_+|', false, '20051021T23:43:00');
+        $data2 = array('zero' => $data1, 'one' => $data1, 'two' => $data1, 'three' => $data1, 'four' => $data1, 'five' => $data1, 'six' => $data1, 'seven' => $data1, 'eight' => $data1, 'nine' => $data1);
+        $data = array($data2, $data2, $data2, $data2, $data2, $data2, $data2, $data2, $data2, $data2);
+        //$keys = array('zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine');
+        $v1 = php_xmlrpc_encode($data, array('auto_dates'));
+        $v2 = php_xmlrpc_decode_xml($v1->serialize());
+        $this->assertEquals($v1, $v2);
+        $r1 = new PhpXmlRpc\Response($v1);
+        $r2 = php_xmlrpc_decode_xml($r1->serialize());
+        $r2->serialize(); // needed to set internal member payload
+        $this->assertEquals($r1, $r2);
+        $m1 = new PhpXmlRpc\Request('hello dolly', array($v1));
+        $m2 = php_xmlrpc_decode_xml($m1->serialize());
+        $m2->serialize(); // needed to set internal member payload
+        $this->assertEquals($m1, $m2);
+    }
+}
similarity index 76%
rename from php/phpxmlrpc/tests/2InvalidHostTest.php
rename to php/phpxmlrpc/tests/4ClientTest.php
index 1c81b55..a2942b4 100644 (file)
@@ -4,34 +4,39 @@ include_once __DIR__ . '/../lib/xmlrpc.inc';
 
 include_once __DIR__ . '/parse_args.php';
 
+include_once __DIR__ . '/PolyfillTestCase.php';
+
+use PHPUnit\Runner\BaseTestRunner;
+
 /**
- * Tests involving requests sent to non-existing servers
+ * Tests involving the Client class.
+ * So far: only tests requests sent to non-existing servers
  */
-class InvalidHostTest extends PHPUnit_Framework_TestCase
+class ClientTest extends PhpXmlRpc_PolyfillTestCase
 {
     /** @var xmlrpc_client $client */
     public $client = null;
     public $args = array();
 
-    public function setUp()
+    public function set_up()
     {
         $this->args = argParser::getArgs();
 
-        $this->client = new xmlrpc_client('/NOTEXIST.php', $this->args['LOCALSERVER'], 80);
+        $this->client = new xmlrpc_client('/NOTEXIST.php', $this->args['HTTPSERVER'], 80);
         $this->client->setDebug($this->args['DEBUG']);
 
         if ($this->args['DEBUG'] == 1)
             ob_start();
     }
 
-    protected function tearDown()
+    protected function tear_down()
     {
         if ($this->args['DEBUG'] != 1)
             return;
         $out = ob_get_clean();
         $status = $this->getStatus();
-        if ($status == PHPUnit_Runner_BaseTestRunner::STATUS_ERROR
-            || $status == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {
+        if ($status == BaseTestRunner::STATUS_ERROR
+            || $status == BaseTestRunner::STATUS_FAILURE) {
             echo $out;
         }
     }
@@ -51,12 +56,12 @@ class InvalidHostTest extends PHPUnit_Framework_TestCase
             new xmlrpcval('hello', 'string'),
         ));
         $this->client->server .= 'XXX';
-        $r = $this->client->send($m, 5);
-        // make sure there's no freaking catchall DNS in effect
-        $dnsinfo = dns_get_record($this->client->server);
+        $dnsinfo = @dns_get_record($this->client->server);
         if ($dnsinfo) {
             $this->markTestSkipped('Seems like there is a catchall DNS in effect: host ' . $this->client->server . ' found');
         } else {
+            $r = $this->client->send($m, 5);
+            // make sure there's no freaking catchall DNS in effect
             $this->assertEquals(5, $r->faultCode());
         }
     }
@@ -79,12 +84,12 @@ class InvalidHostTest extends PHPUnit_Framework_TestCase
         $this->assertTrue($r->faultCode() === 8 || $r->faultCode() == 5);
 
         // now test a successful connection
-        $server = explode(':', $this->args['LOCALSERVER']);
+        $server = explode(':', $this->args['HTTPSERVER']);
         if (count($server) > 1) {
             $this->client->port = $server[1];
         }
         $this->client->server = $server[0];
-        $this->client->path = $this->args['URI'];
+        $this->client->path = $this->args['HTTPURI'];
 
         $r = $this->client->send($m, 5, 'http11');
         $this->assertEquals(0, $r->faultCode());
diff --git a/php/phpxmlrpc/tests/4LocalhostMultiTest.php b/php/phpxmlrpc/tests/4LocalhostMultiTest.php
deleted file mode 100644 (file)
index e5d365a..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-<?php
-
-include_once __DIR__ . '/../lib/xmlrpc.inc';
-include_once __DIR__ . '/../lib/xmlrpc_wrappers.inc';
-
-include_once __DIR__ . '/parse_args.php';
-
-include_once __DIR__ . '/3LocalhostTest.php';
-
-/**
- * Tests which stress http features of the library.
- * Each of these tests iterates over (almost) all of the 'localhost' tests
- */
-class LocalhostMultiTest extends LocalhostTest
-{
-    /**
-     * @todo reintroduce skipping of tests which failed when executed individually if test runs happen as separate processes
-     * @todo reintroduce skipping of tests within the loop
-     */
-    function _runtests()
-    {
-        $unsafeMethods = array('testHttps', 'testCatchExceptions', 'testUtf8Method', 'testServerComments', 'testExoticCharsetsRequests', 'testExoticCharsetsRequests2', 'testExoticCharsetsRequests3');
-        foreach(get_class_methods('LocalhostTest') as $method)
-        {
-            if(strpos($method, 'test') === 0 && !in_array($method, $unsafeMethods))
-            {
-                if (!isset(self::$failed_tests[$method]))
-                    $this->$method();
-            }
-            /*if ($this->_failed)
-            {
-                break;
-            }*/
-        }
-    }
-
-    function testDeflate()
-    {
-        if(!function_exists('gzdeflate'))
-        {
-            $this->markTestSkipped('Zlib missing: cannot test deflate functionality');
-            return;
-        }
-        $this->client->accepted_compression = array('deflate');
-        $this->client->request_compression = 'deflate';
-        $this->_runtests();
-    }
-
-    function testGzip()
-    {
-        if(!function_exists('gzdeflate'))
-        {
-            $this->markTestSkipped('Zlib missing: cannot test gzip functionality');
-            return;
-        }
-        $this->client->accepted_compression = array('gzip');
-        $this->client->request_compression = 'gzip';
-        $this->_runtests();
-    }
-
-    function testKeepAlives()
-    {
-        if(!function_exists('curl_init'))
-        {
-            $this->markTestSkipped('CURL missing: cannot test http 1.1');
-            return;
-        }
-        $this->method = 'http11';
-        $this->client->keepalive = true;
-        $this->_runtests();
-    }
-
-    function testProxy()
-    {
-        if ($this->args['PROXYSERVER'])
-        {
-            $this->client->setProxy($this->args['PROXYSERVER'], $this->args['PROXYPORT']);
-            $this->_runtests();
-        }
-        else
-            $this->markTestSkipped('PROXY definition missing: cannot test proxy');
-    }
-
-    function testHttp11()
-    {
-        if(!function_exists('curl_init'))
-        {
-            $this->markTestSkipped('CURL missing: cannot test http 1.1');
-            return;
-        }
-        $this->method = 'http11'; // not an error the double assignment!
-        $this->client->method = 'http11';
-        //$this->client->verifyhost = 0;
-        //$this->client->verifypeer = 0;
-        $this->client->keepalive = false;
-        $this->_runtests();
-    }
-
-    function testHttp11Gzip()
-    {
-        if(!function_exists('curl_init'))
-        {
-            $this->markTestSkipped('CURL missing: cannot test http 1.1');
-            return;
-        }
-        $this->method = 'http11'; // not an error the double assignment!
-        $this->client->method = 'http11';
-        $this->client->keepalive = false;
-        $this->client->accepted_compression = array('gzip');
-        $this->client->request_compression = 'gzip';
-        $this->_runtests();
-    }
-
-    function testHttp11Deflate()
-    {
-        if(!function_exists('curl_init'))
-        {
-            $this->markTestSkipped('CURL missing: cannot test http 1.1');
-            return;
-        }
-        $this->method = 'http11'; // not an error the double assignment!
-        $this->client->method = 'http11';
-        $this->client->keepalive = false;
-        $this->client->accepted_compression = array('deflate');
-        $this->client->request_compression = 'deflate';
-        $this->_runtests();
-    }
-
-    function testHttp11Proxy()
-    {
-        if(!function_exists('curl_init'))
-        {
-            $this->markTestSkipped('CURL missing: cannot test http 1.1 w. proxy');
-            return;
-        }
-        else if ($this->args['PROXYSERVER'] == '')
-        {
-            $this->markTestSkipped('PROXY definition missing: cannot test proxy w. http 1.1');
-            return;
-        }
-        $this->method = 'http11'; // not an error the double assignment!
-        $this->client->method = 'http11';
-        $this->client->setProxy($this->args['PROXYSERVER'], $this->args['PROXYPORT']);
-        //$this->client->verifyhost = 0;
-        //$this->client->verifypeer = 0;
-        $this->client->keepalive = false;
-        $this->_runtests();
-    }
-
-    function testHttps()
-    {
-        if(!function_exists('curl_init'))
-        {
-            $this->markTestSkipped('CURL missing: cannot test https functionality');
-            return;
-        }
-        $this->client->server = $this->args['HTTPSSERVER'];
-        $this->method = 'https';
-        $this->client->method = 'https';
-        $this->client->path = $this->args['HTTPSURI'];
-        $this->client->setSSLVerifyPeer(!$this->args['HTTPSIGNOREPEER']);
-        $this->client->setSSLVerifyHost($this->args['HTTPSVERIFYHOST']);
-        $this->client->setSSLVersion($this->args['SSLVERSION']);
-        $this->_runtests();
-    }
-
-    function testHttpsProxy()
-    {
-        if(!function_exists('curl_init'))
-        {
-            $this->markTestSkipped('CURL missing: cannot test https functionality');
-            return;
-        }
-        else if ($this->args['PROXYSERVER'] == '')
-        {
-            $this->markTestSkipped('PROXY definition missing: cannot test proxy w. http 1.1');
-            return;
-        }
-        $this->client->server = $this->args['HTTPSSERVER'];
-        $this->method = 'https';
-        $this->client->method = 'https';
-        $this->client->setProxy($this->args['PROXYSERVER'], $this->args['PROXYPORT']);
-        $this->client->path = $this->args['HTTPSURI'];
-        $this->client->setSSLVerifyPeer(!$this->args['HTTPSIGNOREPEER']);
-        $this->client->setSSLVerifyHost($this->args['HTTPSVERIFYHOST']);
-        $this->client->setSSLVersion($this->args['SSLVERSION']);
-        $this->_runtests();
-    }
-
-    function testUTF8Responses()
-    {
-        //$this->client->path = strpos($URI, '?') === null ? $URI.'?RESPONSE_ENCODING=UTF-8' : $URI.'&RESPONSE_ENCODING=UTF-8';
-        $this->client->path = $this->args['URI'].'?RESPONSE_ENCODING=UTF-8';
-        $this->_runtests();
-    }
-
-    function testUTF8Requests()
-    {
-        $this->client->request_charset_encoding = 'UTF-8';
-        $this->_runtests();
-    }
-
-    function testISOResponses()
-    {
-        //$this->client->path = strpos($URI, '?') === null ? $URI.'?RESPONSE_ENCODING=UTF-8' : $URI.'&RESPONSE_ENCODING=UTF-8';
-        $this->client->path = $this->args['URI'].'?RESPONSE_ENCODING=ISO-8859-1';
-        $this->_runtests();
-    }
-
-    function testISORequests()
-    {
-        $this->client->request_charset_encoding = 'ISO-8859-1';
-        $this->_runtests();
-    }
-}
similarity index 88%
rename from php/phpxmlrpc/tests/3LocalhostTest.php
rename to php/phpxmlrpc/tests/5ServerTest.php
index 0290dbf..0345b79 100644 (file)
@@ -5,11 +5,18 @@ include_once __DIR__ . '/../lib/xmlrpc_wrappers.inc';
 
 include_once __DIR__ . '/parse_args.php';
 
+include_once __DIR__ . '/PolyfillTestCase.php';
+
+use PHPUnit\Extensions\SeleniumCommon\RemoteCoverage;
+use PHPUnit\Framework\TestResult;
+use PHPUnit\Runner\BaseTestRunner;
+
 /**
  * Tests which involve interaction between the client and the server.
- * They are run against the server found in demo/server.php
+ * They are run against the server found in demo/server.php.
+ * Includes testing of (some of) the Wrapper class
  */
-class LocalhostTest extends PHPUnit_Framework_TestCase
+class ServerTest extends PhpXmlRpc_PolyfillTestCase
 {
     /** @var xmlrpc_client $client */
     protected $client = null;
@@ -26,7 +33,10 @@ class LocalhostTest extends PHPUnit_Framework_TestCase
     protected $collectCodeCoverageInformation;
     protected $coverageScriptUrl;
 
-    public static function fail($message = '')
+    /**
+     * @todo instead of overriding fail via _fail, implement Yoast\PHPUnitPolyfills\TestListeners\TestListenerDefaultImplementation
+     */
+    public static function _fail($message = '')
     {
         // save in a static var that this particular test has failed
         // (but only if not called from subclass objects / multitests)
@@ -40,18 +50,20 @@ class LocalhostTest extends PHPUnit_Framework_TestCase
             }
         }
 
-        parent::fail($message);
+        parent::_fail($message);
     }
 
     /**
      * Reimplemented to allow us to collect code coverage info from the target server.
      * Code taken from PHPUnit_Extensions_Selenium2TestCase
      *
-     * @param PHPUnit_Framework_TestResult $result
-     * @return PHPUnit_Framework_TestResult
+     * @param TestResult $result
+     * @return TestResult
      * @throws Exception
+     *
+     * @todo instead of overriding run via _run, try to achieve this by implementing Yoast\PHPUnitPolyfills\TestListeners\TestListenerDefaultImplementation
      */
-    public function run(PHPUnit_Framework_TestResult $result = NULL)
+    public function _run($result = NULL)
     {
         $this->testId = get_class($this) . '__' . $this->getName();
 
@@ -61,10 +73,10 @@ class LocalhostTest extends PHPUnit_Framework_TestCase
 
         $this->collectCodeCoverageInformation = $result->getCollectCodeCoverageInformation();
 
-        parent::run($result);
+        parent::_run($result);
 
         if ($this->collectCodeCoverageInformation) {
-            $coverage = new PHPUnit_Extensions_SeleniumCommon_RemoteCoverage(
+            $coverage = new RemoteCoverage(
                 $this->coverageScriptUrl,
                 $this->testId
             );
@@ -79,44 +91,44 @@ class LocalhostTest extends PHPUnit_Framework_TestCase
         return $result;
     }
 
-    public function setUp()
+    public function set_up()
     {
         $this->args = argParser::getArgs();
 
-        $server = explode(':', $this->args['LOCALSERVER']);
+        $server = explode(':', $this->args['HTTPSERVER']);
         if (count($server) > 1) {
-            $this->client = new xmlrpc_client($this->args['URI'], $server[0], $server[1]);
+            $this->client = new xmlrpc_client($this->args['HTTPURI'], $server[0], $server[1]);
         } else {
-            $this->client = new xmlrpc_client($this->args['URI'], $this->args['LOCALSERVER']);
+            $this->client = new xmlrpc_client($this->args['HTTPURI'], $this->args['HTTPSERVER']);
         }
 
         $this->client->setDebug($this->args['DEBUG']);
         $this->client->request_compression = $this->request_compression;
         $this->client->accepted_compression = $this->accepted_compression;
 
-        $this->coverageScriptUrl = 'http://' . $this->args['LOCALSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['URI'] );
+        $this->coverageScriptUrl = 'http://' . $this->args['HTTPSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['HTTPURI'] );
 
         if ($this->args['DEBUG'] == 1)
             ob_start();
     }
 
-    protected function tearDown()
+    protected function tear_down()
     {
         if ($this->args['DEBUG'] != 1)
             return;
         $out = ob_get_clean();
         $status = $this->getStatus();
-        if ($status == PHPUnit_Runner_BaseTestRunner::STATUS_ERROR
-            || $status == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {
+        if ($status == BaseTestRunner::STATUS_ERROR
+            || $status == BaseTestRunner::STATUS_FAILURE) {
             echo $out;
         }
     }
 
     /**
      * @param PhpXmlRpc\Request|array $msg
-     * @param int|array $errorCode
+     * @param int|array $errorCode expected error codes
      * @param bool $returnResponse
-     * @return mixed|\PhpXmlRpc\Response|\PhpXmlRpc\Response[]|\PhpXmlRpc\Value|string|void
+     * @return mixed|\PhpXmlRpc\Response|\PhpXmlRpc\Response[]|\PhpXmlRpc\Value|string|null
      */
     protected function send($msg, $errorCode = 0, $returnResponse = false)
     {
@@ -141,10 +153,22 @@ class LocalhostTest extends PHPUnit_Framework_TestCase
                 return $r->value();
             }
         } else {
-            return;
+            return null;
         }
     }
 
+    /**
+     * Adds (and replaces) query params to the url currently used by the client
+     * @param array $data
+     */
+    protected function addQueryParams($data)
+    {
+        $query = parse_url($this->client->path, PHP_URL_QUERY);
+        parse_str($query, $vars);
+        $query = http_build_query(array_merge($vars, $data));
+        $this->client->path = parse_url($this->client->path, PHP_URL_PATH) . '?' . $query;
+    }
+
     public function testString()
     {
         $sendString = "here are 3 \"entities\": < > & " .
@@ -235,7 +259,7 @@ class LocalhostTest extends PHPUnit_Framework_TestCase
         PhpXmlRpc\PhpXmlRpc::$xmlrpc_internalencoding = 'UTF-8';
         // no encoding declaration either in the http header or xml prolog, let mb_detect_encoding
         // (used on the server side) sort it out
-        $this->client->path = $this->args['URI'].'?DETECT_ENCODINGS[]=EUC-JP&DETECT_ENCODINGS[]=UTF-8';
+        $this->addQueryParams(array('DETECT_ENCODINGS' => array('EUC-JP', 'UTF-8')));
         $v = $this->send(mb_convert_encoding($str, 'EUC-JP', 'UTF-8'));
         $this->assertEquals($sendString, $v->scalarval());
         PhpXmlRpc\PhpXmlRpc::$xmlrpc_internalencoding = 'ISO-8859-1';
@@ -261,7 +285,7 @@ class LocalhostTest extends PHPUnit_Framework_TestCase
 
         // no encoding declaration either in the http header or xml prolog, let mb_detect_encoding
         // (used on the server side) sort it out
-        $this->client->path = $this->args['URI'].'?DETECT_ENCODINGS[]=ISO-8859-1&DETECT_ENCODINGS[]=UTF-8';
+        $this->addQueryParams(array('DETECT_ENCODINGS' => array('ISO-8859-1', 'UTF-8')));
         $v = $this->send($str);
         $this->assertEquals($sendString, $v->scalarval());
     }
@@ -411,7 +435,7 @@ And turned it into nylon';
             $got = '';
             $expected = '37210';
             $expect_array = array('ctLeftAngleBrackets', 'ctRightAngleBrackets', 'ctAmpersands', 'ctApostrophes', 'ctQuotes');
-            while (list(, $val) = each($expect_array)) {
+            foreach($expect_array as $val) {
                 $b = $v->structmem($val);
                 $got .= $b->me['int'];
             }
@@ -488,6 +512,7 @@ And turned it into nylon';
     {
         // NB: This test will NOT pass if server does not support system.multicall.
 
+        $noMultiCall = $this->client->no_multicall;
         $this->client->no_multicall = false;
 
         $good1 = new xmlrpcmsg('system.methodHelp',
@@ -525,12 +550,15 @@ And turned it into nylon';
         $this->assertTrue($this->client->no_multicall == false,
             "server does not support system.multicall"
         );
+
+        $this->client->no_multicall = $noMultiCall;
     }
 
     public function testClientMulticall2()
     {
         // NB: This test will NOT pass if server does not support system.multicall.
 
+        $noMultiCall = $this->client->no_multicall;
         $this->client->no_multicall = true;
 
         $good1 = new xmlrpcmsg('system.methodHelp',
@@ -562,12 +590,17 @@ And turned it into nylon';
             $val = $r[3]->value();
             $this->assertTrue($val->kindOf() == 'array', "good2 did not return array");
         }
+
+        $this->client->no_multicall = $noMultiCall;
     }
 
     public function testClientMulticall3()
     {
         // NB: This test will NOT pass if server does not support system.multicall.
 
+        $noMultiCall = $this->client->no_multicall;
+        $returnType = $this->client->return_type;
+
         $this->client->return_type = 'phpvals';
         $this->client->no_multicall = false;
 
@@ -598,7 +631,9 @@ And turned it into nylon';
             $val = $r[3]->value();
             $this->assertTrue(is_array($val), "good2 did not return array");
         }
-        $this->client->return_type = 'xmlrpcvals';
+
+        $this->client->return_type = $returnType;
+        $this->client->no_multicall = $noMultiCall;
     }
 
     public function testCatchWarnings()
@@ -618,9 +653,9 @@ And turned it into nylon';
             new xmlrpcval('whatever', 'string'),
         ));
         $v = $this->send($m, $GLOBALS['xmlrpcerr']['server_error']);
-        $this->client->path = $this->args['URI'] . '?EXCEPTION_HANDLING=1';
+        $this->addQueryParams(array('EXCEPTION_HANDLING' => 1));
         $v = $this->send($m, 1); // the error code of the expected exception
-        $this->client->path = $this->args['URI'] . '?EXCEPTION_HANDLING=2';
+        $this->addQueryParams(array('EXCEPTION_HANDLING' => 2));
         // depending on whether display_errors is ON or OFF on the server, we will get back a different error here,
         // as php will generate an http status code of either 200 or 500...
         $v = $this->send($m, array($GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcerr']['http_error']));
@@ -791,6 +826,30 @@ And turned it into nylon';
         $this->assertEquals('Michigan', $v->scalarval());
     }
 
+    public function testServerWrappedClassWithNamespace()
+    {
+        $m = new xmlrpcmsg('namespacetest.findState', array(
+            new xmlrpcval(23, 'int'),
+        ));
+        $v = $this->send($m);
+        $this->assertEquals('Michigan', $v->scalarval());
+    }
+
+    public function testWrapInexistentMethod()
+    {
+        // make a 'deep client copy' as the original one might have many properties set
+        $func = wrap_xmlrpc_method($this->client, 'examples.getStateName.notexisting', array('simple_client_copy' => 0));
+        $this->assertEquals(false, $func);
+    }
+
+    public function testWrapInexistentUrl()
+    {
+        $this->client->path = '/notexisting';
+        // make a 'deep client copy' as the original one might have many properties set
+        $func = wrap_xmlrpc_method($this->client, 'examples.getStateName', array('simple_client_copy' => 0));
+        $this->assertEquals(false, $func);
+    }
+
     public function testWrappedMethod()
     {
         // make a 'deep client copy' as the original one might have many properties set
@@ -834,19 +893,23 @@ And turned it into nylon';
             $this->fail('Registration of remote server failed');
         } else {
             $obj = new $class();
-            $v = $obj->examples_getStateName(23);
-            // work around bug in current (or old?) version of phpunit when reporting the error
-            /*if (is_object($v)) {
-                $v = var_export($v, true);
-            }*/
-            $this->assertEquals('Michigan', $v);
+            if (!is_callable(array($obj, 'examples_getStateName'))) {
+                $this->fail('Registration of remote server failed to import method "examples_getStateName"');
+            } else {
+                $v = $obj->examples_getStateName(23);
+                // work around bug in current (or old?) version of phpunit when reporting the error
+                /*if (is_object($v)) {
+                    $v = var_export($v, true);
+                }*/
+                $this->assertEquals('Michigan', $v);
+            }
         }
     }
 
     public function testTransferOfObjectViaWrapping()
     {
         // make a 'deep client copy' as the original one might have many properties set
-        $func = wrap_xmlrpc_method($this->client, 'tests.returnPhpObject', array('simple_client_copy' => true,
+        $func = wrap_xmlrpc_method($this->client, 'tests.returnPhpObject', array('simple_client_copy' => 0,
             'decode_php_objs' => true));
         if ($func == false) {
             $this->fail('Registration of tests.returnPhpObject failed');
@@ -938,7 +1001,7 @@ And turned it into nylon';
             new xmlrpcval('hello world', 'string'),
         ));
         $r = $this->send($m, 0, true);
-        $this->assertContains('hello world', $r->raw_data);
+        $this->assertStringContainsString('hello world', $r->raw_data);
     }
 
     public function testSendTwiceSameMsg()
diff --git a/php/phpxmlrpc/tests/6HTTPTest.php b/php/phpxmlrpc/tests/6HTTPTest.php
new file mode 100644 (file)
index 0000000..3a14809
--- /dev/null
@@ -0,0 +1,381 @@
+<?php
+
+include_once __DIR__ . '/../lib/xmlrpc.inc';
+include_once __DIR__ . '/../lib/xmlrpc_wrappers.inc';
+
+include_once __DIR__ . '/parse_args.php';
+
+include_once __DIR__ . '/5ServerTest.php';
+
+/**
+ * Tests which stress http features of the library.
+ * Each of these tests iterates over (almost) all the 'localhost' tests
+ */
+class HTTPTest extends ServerTest
+{
+    /**
+     * Returns all test methods from the base class, except the ones which failed already
+     *
+     * @todo (re)introduce skipping of tests which failed when executed individually even if test runs happen as separate processes
+     * @todo reintroduce skipping of tests within the loop
+     */
+    public function getSingleHttpTestMethods()
+    {
+        $unsafeMethods = array(
+            'testCatchExceptions', 'testUtf8Method', 'testServerComments',
+            'testExoticCharsetsRequests', 'testExoticCharsetsRequests2', 'testExoticCharsetsRequests3',
+            'testWrapInexistentUrl',
+        );
+
+        $methods = array();
+        foreach(get_class_methods('ServerTest') as $method)
+        {
+            if (strpos($method, 'test') === 0 && !in_array($method, $unsafeMethods))
+            {
+                if (!isset(self::$failed_tests[$method])) {
+                    $methods[$method] = array($method);
+                }
+            }
+        }
+
+        return $methods;
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testDeflate($method)
+    {
+        if (!function_exists('gzdeflate'))
+        {
+            $this->markTestSkipped('Zlib missing: cannot test deflate functionality');
+            return;
+        }
+
+        $this->client->accepted_compression = array('deflate');
+        $this->client->request_compression = 'deflate';
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testGzip($method)
+    {
+        if (!function_exists('gzdeflate'))
+        {
+            $this->markTestSkipped('Zlib missing: cannot test gzip functionality');
+            return;
+        }
+
+        $this->client->accepted_compression = array('gzip');
+        $this->client->request_compression = 'gzip';
+
+        $this->$method();
+    }
+
+    public function testKeepAlives()
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test http 1.1');
+            return;
+        }
+
+        $this->method = 'http11';
+        $this->client->method = 'http11';
+        $this->client->keepalive = true;
+
+        // to successfully test keepalive, we have to reuse the same client for all tests, we can not recreate one on setup/teardown...
+        foreach ($this->getSingleHttpTestMethods() as $methods) {
+            $method = $methods[0];
+            $this->$method();
+        }
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testProxy($method)
+    {
+        if ($this->args['PROXYSERVER'] == '')
+        {
+            $this->markTestSkipped('PROXYSERVER definition missing: cannot test proxy');
+            return;
+        }
+
+        $this->client->setProxy($this->args['PROXYSERVER'], $this->args['PROXYPORT']);
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttp11($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test http 1.1');
+            return;
+        }
+
+        $this->method = 'http11'; // not an error the double assignment!
+        $this->client->method = 'http11';
+        $this->client->keepalive = false;
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttp10Curl($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test http 1.1');
+            return;
+        }
+
+        $this->method = 'http10'; // not an error the double assignment!
+        $this->client->method = 'http10';
+        $this->client->keepalive = false;
+        $this->client->setUseCurl(\PhpXmlRpc\Client::USE_CURL_ALWAYS);
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttp11Gzip($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test http 1.1');
+            return;
+        }
+        $this->method = 'http11'; // not an error the double assignment!
+        $this->client->method = 'http11';
+        $this->client->keepalive = false;
+        $this->client->accepted_compression = array('gzip');
+        $this->client->request_compression = 'gzip';
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttp11Deflate($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test http 1.1');
+            return;
+        }
+        $this->method = 'http11'; // not an error the double assignment!
+        $this->client->method = 'http11';
+        $this->client->keepalive = false;
+        $this->client->accepted_compression = array('deflate');
+        $this->client->request_compression = 'deflate';
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttp11Proxy($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test http 1.1 w. proxy');
+            return;
+        }
+        else if ($this->args['PROXYSERVER'] == '')
+        {
+            $this->markTestSkipped('PROXYSERVER definition missing: cannot test proxy w. http 1.1');
+            return;
+        }
+
+        $this->method = 'http11'; // not an error the double assignment!
+        $this->client->method = 'http11';
+        $this->client->setProxy($this->args['PROXYSERVER'], $this->args['PROXYPORT']);
+        $this->client->keepalive = false;
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttps($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test https functionality');
+            return;
+        }
+        else if ($this->args['HTTPSSERVER'] == '')
+        {
+            $this->markTestSkipped('HTTPS SERVER definition missing: cannot test https');
+            return;
+        }
+
+        $this->client->server = $this->args['HTTPSSERVER'];
+        $this->method = 'https';
+        $this->client->method = 'https';
+        $this->client->path = $this->args['HTTPSURI'];
+        $this->client->setSSLVerifyPeer(!$this->args['HTTPSIGNOREPEER']);
+        $this->client->setSSLVerifyHost($this->args['HTTPSVERIFYHOST']);
+        $this->client->setSSLVersion($this->args['SSLVERSION']);
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttpsSocket($method)
+    {
+        if ($this->args['HTTPSSERVER'] == '')
+        {
+            $this->markTestSkipped('HTTPS SERVER definition missing: cannot test https');
+            return;
+        }
+
+        $this->client->server = $this->args['HTTPSSERVER'];
+        $this->method = 'https';
+        $this->client->method = 'https';
+        $this->client->path = $this->args['HTTPSURI'];
+        $this->client->setSSLVerifyPeer(!$this->args['HTTPSIGNOREPEER']);
+        $this->client->setSSLVerifyHost($this->args['HTTPSVERIFYHOST']);
+        $this->client->setSSLVersion($this->args['SSLVERSION']);
+        $this->client->setUseCurl(\PhpXmlRpc\Client::USE_CURL_NEVER);
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testHttpsProxy($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test https w. proxy');
+            return;
+        }
+        else if ($this->args['PROXYSERVER'] == '')
+        {
+            $this->markTestSkipped('PROXYSERVER definition missing: cannot test proxy w. https');
+            return;
+        }
+        else if ($this->args['HTTPSSERVER'] == '')
+        {
+            $this->markTestSkipped('HTTPS SERVER definition missing: cannot test https w. proxy');
+            return;
+        }
+
+        $this->client->server = $this->args['HTTPSSERVER'];
+        $this->method = 'https';
+        $this->client->method = 'https';
+        $this->client->setProxy($this->args['PROXYSERVER'], $this->args['PROXYPORT']);
+        $this->client->path = $this->args['HTTPSURI'];
+        $this->client->setSSLVerifyPeer(!$this->args['HTTPSIGNOREPEER']);
+        $this->client->setSSLVerifyHost($this->args['HTTPSVERIFYHOST']);
+        $this->client->setSSLVersion($this->args['SSLVERSION']);
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testUTF8Responses($method)
+    {
+        $this->addQueryParams(array('RESPONSE_ENCODING' => 'UTF-8'));
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testUTF8Requests($method)
+    {
+        $this->client->request_charset_encoding = 'UTF-8';
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testISOResponses($method)
+    {
+        $this->addQueryParams(array('RESPONSE_ENCODING' => 'ISO-8859-1'));
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testISORequests($method)
+    {
+        $this->client->request_charset_encoding = 'ISO-8859-1';
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testBasicAuth($method)
+    {
+        $this->client->setCredentials('test', 'test');
+        $this->addQueryParams(array('FORCE_AUTH' => 'Basic'));
+
+        $this->$method();
+    }
+
+    /**
+     * @dataProvider getSingleHttpTestMethods
+     * @param string $method
+     */
+    public function testDigestAuth($method)
+    {
+        if (!function_exists('curl_init'))
+        {
+            $this->markTestSkipped('CURL missing: cannot test digest auth functionality');
+            return;
+        }
+
+        $this->client->setCredentials('test', 'test', CURLAUTH_DIGEST);
+        $this->addQueryParams(array('FORCE_AUTH' => 'Digest'));
+        $this->method = 'http11';
+        $this->client->method = 'http11';
+
+        $this->$method();
+    }
+}
similarity index 68%
rename from php/phpxmlrpc/tests/5DemofilesTest.php
rename to php/phpxmlrpc/tests/7DemofilesTest.php
index 3cbb5b4..7c4b775 100644 (file)
@@ -1,19 +1,19 @@
 <?php
 
-include_once __DIR__ . '/LocalFileTestCase.php';
+include_once __DIR__ . '/WebTestCase.php';
 
 /**
  * Tests for php files in the 'demo' directory
  */
-class DemoFilesTest extends PhpXmlRpc_LocalFileTestCase
+class DemoFilesTest extends PhpXmlRpc_WebTestCase
 {
-    public function setUp()
+    public function set_up()
     {
         $this->args = argParser::getArgs();
 
-        $this->baseUrl = $this->args['LOCALSERVER'] . str_replace( '/demo/server/server.php', '/demo/', $this->args['URI'] );
+        $this->baseUrl = $this->args['HTTPSERVER'] . str_replace( '/demo/server/server.php', '/demo/', $this->args['HTTPURI'] );
 
-        $this->coverageScriptUrl = 'http://' . $this->args['LOCALSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['URI'] );
+        $this->coverageScriptUrl = 'http://' . $this->args['HTTPSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['HTTPURI'] );
     }
 
     public function testAgeSort()
@@ -62,15 +62,17 @@ class DemoFilesTest extends PhpXmlRpc_LocalFileTestCase
 
     public function testDiscussServer()
     {
+        /// @todo add a couple of proper xmlrpc calls, too
         $page = $this->request('server/discuss.php');
-        $this->assertContains('<name>faultCode</name>', $page);
+        $this->assertStringContainsString('<name>faultCode</name>', $page);
         $this->assertRegexp('#<int>10(5|3)</int>#', $page);
     }
 
     public function testProxyServer()
     {
+        /// @todo add a couple of proper xmlrpc calls, too
         $page = $this->request('server/proxy.php');
-        $this->assertContains('<name>faultCode</name>', $page);
+        $this->assertStringContainsString('<name>faultCode</name>', $page);
         $this->assertRegexp('#<int>10(5|3)</int>#', $page);
     }
 }
diff --git a/php/phpxmlrpc/tests/7ExtraTest.php b/php/phpxmlrpc/tests/7ExtraTest.php
deleted file mode 100644 (file)
index 14488e8..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-include_once __DIR__ . '/LocalFileTestCase.php';
-
-/**
- * Tests for php files in the 'extras' directory
- */
-class ExtraTest extends PhpXmlRpc_LocalFileTestCase
-{
-    public function setUp()
-    {
-        $this->args = argParser::getArgs();
-
-        $this->baseUrl = $this->args['LOCALSERVER'] . str_replace( '/demo/server/server.php', '/tests/', $this->args['URI'] );
-
-        $this->coverageScriptUrl = 'http://' . $this->args['LOCALSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['URI'] );
-    }
-
-    public function testVerifyCompat()
-    {
-        $page = $this->request('verify_compat.php');
-    }
-}
\ No newline at end of file
similarity index 52%
rename from php/phpxmlrpc/tests/6DebuggerTest.php
rename to php/phpxmlrpc/tests/8DebuggerTest.php
index db0e850..779ad47 100644 (file)
@@ -1,16 +1,16 @@
 <?php
 
-include_once __DIR__ . '/LocalFileTestCase.php';
+include_once __DIR__ . '/WebTestCase.php';
 
-class DebuggerTest extends PhpXmlRpc_LocalFileTestCase
+class DebuggerTest extends PhpXmlRpc_WebTestCase
 {
-    public function setUp()
+    public function set_up()
     {
         $this->args = argParser::getArgs();
 
-        $this->baseUrl = $this->args['LOCALSERVER'] . str_replace( '/demo/server/server.php', '/debugger/', $this->args['URI'] );
+        $this->baseUrl = $this->args['HTTPSERVER'] . str_replace( '/demo/server/server.php', '/debugger/', $this->args['HTTPURI'] );
 
-        $this->coverageScriptUrl = 'http://' . $this->args['LOCALSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['URI'] );
+        $this->coverageScriptUrl = 'http://' . $this->args['HTTPSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['HTTPURI'] );
     }
 
     public function testIndex()
diff --git a/php/phpxmlrpc/tests/9ExtraFilesTest.php b/php/phpxmlrpc/tests/9ExtraFilesTest.php
new file mode 100644 (file)
index 0000000..a88bace
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+include_once __DIR__ . '/WebTestCase.php';
+
+/**
+ * Tests for php files in the 'extras' directory
+ *
+ */
+class ExtraFilesTest extends PhpXmlRpc_WebTestCase
+{
+    public function set_up()
+    {
+        $this->args = argParser::getArgs();
+
+        $this->baseUrl = $this->args['HTTPSERVER'] . str_replace( '/demo/server/server.php', '/extras/', $this->args['HTTPURI'] );
+
+        $this->coverageScriptUrl = 'http://' . $this->args['HTTPSERVER'] . '/' . str_replace( '/demo/server/server.php', 'tests/phpunit_coverage.php', $this->args['HTTPURI'] );
+    }
+
+    /**
+     * @todo collect code coverage for this...
+     */
+    public function testBenchmark()
+    {
+        $page = $this->request('benchmark.php');
+    }
+
+    /**
+     * @todo collect code coverage for this...
+     */
+    public function testVerifyCompat()
+    {
+        $page = $this->request('verify_compat.php');
+    }
+
+    public function testVarDemo()
+    {
+        $this->baseUrl = str_replace('/extras/', '/demo/', $this->baseUrl);
+        $page = $this->request('vardemo.php');
+    }
+}
diff --git a/php/phpxmlrpc/tests/PolyfillTestCase.php b/php/phpxmlrpc/tests/PolyfillTestCase.php
new file mode 100644 (file)
index 0000000..24957fa
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+use PHPUnit\Runner\Version as PHPUnit_Version;
+
+if ( class_exists( 'PHPUnit_Extensions_SeleniumCommon_RemoteCoverage' ) === true
+    && class_exists( 'PHPUnit\Extensions\SeleniumCommon\RemoteCoverage' ) === false
+) {
+    class_alias( 'PHPUnit_Extensions_SeleniumCommon_RemoteCoverage', 'PHPUnit\Extensions\SeleniumCommon\RemoteCoverage' );
+}
+
+if ( class_exists( 'PHPUnit_Runner_BaseTestRunner' ) === true
+    && class_exists( 'PHPUnit\Runner\BaseTestRunner' ) === false
+) {
+    class_alias( 'PHPUnit_Runner_BaseTestRunner', 'PHPUnit\Runner\BaseTestRunner' );
+}
+
+if (class_exists(PHPUnit_Version::class) === false || version_compare(PHPUnit_Version::id(), '8.0.0', '<')) {
+    include_once __DIR__ . '/PolyfillTestCase7.php';
+} else {
+    include_once __DIR__ . '/PolyfillTestCase8.php';
+}
diff --git a/php/phpxmlrpc/tests/PolyfillTestCase7.php b/php/phpxmlrpc/tests/PolyfillTestCase7.php
new file mode 100644 (file)
index 0000000..d54d6b2
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+
+use Yoast\PHPUnitPolyfills\TestCases\TestCase;
+
+abstract class PhpXmlRpc_PolyfillTestCase extends TestCase
+{
+    public function _run($result = null) {
+        return parent::run($result);
+    }
+
+    public static function _fail() {}
+
+    public function run(PHPUnit_Framework_TestResult $result = null) {
+        return $this->_run($result);
+    }
+
+    public static function fail($message = '') {
+        static::_fail($message);
+        self::fail($message);
+    }
+}
diff --git a/php/phpxmlrpc/tests/PolyfillTestCase8.php b/php/phpxmlrpc/tests/PolyfillTestCase8.php
new file mode 100644 (file)
index 0000000..5c8711a
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+use PHPUnit\Framework\TestResult as PHPUnit_Framework_TestResult;
+use Yoast\PHPUnitPolyfills\TestCases\TestCase;
+
+abstract class PhpXmlRpc_PolyfillTestCase extends TestCase
+{
+    public function _run($result = null) {
+        return parent::run($result);
+    }
+
+    public static function _fail() {}
+
+    public function run(PHPUnit_Framework_TestResult $result = null): PHPUnit_Framework_TestResult {
+        return $this->_run($result);
+    }
+
+    public static function fail(string $message = ''): void {
+        static::_fail($message);
+        parent::fail($message);
+    }
+}
similarity index 62%
rename from php/phpxmlrpc/tests/LocalFileTestCase.php
rename to php/phpxmlrpc/tests/WebTestCase.php
index 59818c8..f4818b3 100644 (file)
@@ -2,7 +2,11 @@
 
 include_once __DIR__ . '/parse_args.php';
 
-abstract class PhpXmlRpc_LocalFileTestCase extends PHPUnit_Framework_TestCase
+include_once __DIR__ . '/PolyfillTestCase.php';
+
+use PHPUnit\Extensions\SeleniumCommon\RemoteCoverage;
+
+abstract class PhpXmlRpc_WebTestCase extends PhpXmlRpc_PolyfillTestCase
 {
     public $args = array();
 
@@ -13,7 +17,13 @@ abstract class PhpXmlRpc_LocalFileTestCase extends PHPUnit_Framework_TestCase
     protected $collectCodeCoverageInformation;
     protected $coverageScriptUrl;
 
-    public function run(PHPUnit_Framework_TestResult $result = NULL)
+    /**
+     * Reimplemented to allow us to collect code coverage info for the target php files executed via an http request.
+     * Code taken from PHPUnit_Extensions_Selenium2TestCase
+     *
+     * @todo instead of overriding run via _run, try to achieve this by implementing Yoast\PHPUnitPolyfills\TestListeners\TestListenerDefaultImplementation
+     */
+    public function _run($result = NULL)
     {
         $this->testId = get_class($this) . '__' . $this->getName();
 
@@ -23,10 +33,10 @@ abstract class PhpXmlRpc_LocalFileTestCase extends PHPUnit_Framework_TestCase
 
         $this->collectCodeCoverageInformation = $result->getCollectCodeCoverageInformation();
 
-        parent::run($result);
+        parent::_run($result);
 
         if ($this->collectCodeCoverageInformation) {
-            $coverage = new PHPUnit_Extensions_SeleniumCommon_RemoteCoverage(
+            $coverage = new RemoteCoverage(
                 $this->coverageScriptUrl,
                 $this->testId
             );
@@ -41,9 +51,16 @@ abstract class PhpXmlRpc_LocalFileTestCase extends PHPUnit_Framework_TestCase
         return $result;
     }
 
-    protected function request($file, $method = 'GET', $payload = '', $emptyPageOk = false)
+    /**
+     * @param string $path
+     * @param string $method
+     * @param string $payload
+     * @param false $emptyPageOk
+     * @return bool|string
+     */
+    protected function request($path, $method = 'GET', $payload = '', $emptyPageOk = false)
     {
-        $url = $this->baseUrl . $file;
+        $url = $this->baseUrl . $path;
 
         $ch = curl_init($url);
         curl_setopt_array($ch, array(
@@ -59,7 +76,7 @@ abstract class PhpXmlRpc_LocalFileTestCase extends PHPUnit_Framework_TestCase
         }
         if ($this->collectCodeCoverageInformation)
         {
-            curl_setopt($ch, CURLOPT_COOKIE, 'PHPUNIT_SELENIUM_TEST_ID=true');
+            curl_setopt($ch, CURLOPT_COOKIE, 'PHPUNIT_SELENIUM_TEST_ID='.$this->testId);
         }
         if ($this->args['DEBUG'] > 0) {
             curl_setopt($ch, CURLOPT_VERBOSE, 1);
@@ -71,10 +88,9 @@ abstract class PhpXmlRpc_LocalFileTestCase extends PHPUnit_Framework_TestCase
         if (!$emptyPageOk) {
             $this->assertNotEquals('', $page);
         }
-        $this->assertNotContains('Fatal error', $page);
-        $this->assertNotContains('Notice:', $page);
+        $this->assertStringNotContainsStringIgnoringCase('Fatal error', $page);
+        $this->assertStringNotContainsStringIgnoringCase('Notice:', $page);
 
         return $page;
     }
-
 }
diff --git a/php/phpxmlrpc/tests/ci/Dockerfile b/php/phpxmlrpc/tests/ci/Dockerfile
new file mode 100644 (file)
index 0000000..d8c4443
--- /dev/null
@@ -0,0 +1,29 @@
+ARG UBUNTU_VERSION=bionic
+
+FROM ubuntu:${UBUNTU_VERSION}
+
+ARG PHP_VERSION=default
+
+COPY setup/*.sh /root/setup/
+COPY config/* /root/config/
+
+RUN mkdir -p /usr/share/man/man1 && \
+  apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y upgrade && \
+  chmod 755 /root/setup/*.sh && \
+  cd /root/setup && \
+  ./install_packages.sh && \
+  ./create_user.sh && \
+  ./setup_apache.sh && \
+  ./setup_privoxy.sh && \
+  ./setup_php.sh "${PHP_VERSION}" && \
+  ./setup_composer.sh
+
+COPY docker/entrypoint.sh /root/entrypoint.sh
+RUN chmod 755 /root/entrypoint.sh
+
+EXPOSE 80 443 8080
+
+# @todo can we avoid hardcoding this here? We can f.e. get it passed down as ARG...
+WORKDIR /home/docker/build
+
+ENTRYPOINT ["/root/entrypoint.sh"]
diff --git a/php/phpxmlrpc/tests/ci/config/apache_phpfpm_proxyfcgi b/php/phpxmlrpc/tests/ci/config/apache_phpfpm_proxyfcgi
new file mode 100644 (file)
index 0000000..8bed636
--- /dev/null
@@ -0,0 +1,27 @@
+# @todo check: templatize this, to make it work with any php version
+
+# Redirect to local php-fpm if mod_php is not available
+<IfModule !mod_php8.c>
+<IfModule !mod_php7.c>
+<IfModule proxy_fcgi_module>
+    # Enable http authorization headers
+    <IfModule setenvif_module>
+        SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
+    </IfModule>
+
+    <FilesMatch ".+\.ph(ar|p|tml)$">
+        SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost"
+    </FilesMatch>
+    <FilesMatch ".+\.phps$">
+        # Deny access to raw php sources by default
+        # To re-enable it's recommended to enable access to the files
+        # only in specific virtual host or directory
+        Require all denied
+    </FilesMatch>
+    # Deny access to files without filename (e.g. '.php')
+    <FilesMatch "^\.ph(ar|p|ps|tml)$">
+        Require all denied
+    </FilesMatch>
+</IfModule>
+</IfModule>
+</IfModule>
diff --git a/php/phpxmlrpc/tests/ci/config/apache_vhost b/php/phpxmlrpc/tests/ci/config/apache_vhost
new file mode 100644 (file)
index 0000000..88307bf
--- /dev/null
@@ -0,0 +1,69 @@
+# Uses env vars:
+# HTTPSERVER
+# TESTS_ROOT_DIR
+
+<VirtualHost *:80>
+
+  DocumentRoot ${TESTS_ROOT_DIR}
+
+  #ErrorLog "${TESTS_ROOT_DIR}/apache_error.log"
+  #CustomLog "${TESTS_ROOT_DIR}/apache_access.log" combined
+
+  # Env vars used by the test code, which we get from the environment
+  SetEnv HTTPSERVER ${HTTPSERVER}
+
+  <Directory "${TESTS_ROOT_DIR}">
+    Options FollowSymLinks MultiViews
+    AllowOverride All
+
+    Require all granted
+
+    # needed for basic auth (PHP_AUTH_USER and PHP_AUTH_PW)
+    RewriteEngine on
+    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+    RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
+  </Directory>
+
+</VirtualHost>
+
+<IfModule mod_ssl.c>
+
+<VirtualHost _default_:443>
+
+  DocumentRoot ${TESTS_ROOT_DIR}
+
+  #ErrorLog "${TESTS_ROOT_DIR}/apache_error.log"
+  #CustomLog "${TESTS_ROOT_DIR}/apache_access.log" combined
+
+  # Env vars used by the test code, which we get from the environment
+  SetEnv HTTPSERVER ${HTTPSERVER}
+
+  <Directory "${TESTS_ROOT_DIR}">
+    Options FollowSymLinks MultiViews
+    AllowOverride All
+
+    Require all granted
+
+    # needed for basic auth (PHP_AUTH_USER and PHP_AUTH_PW)
+    RewriteEngine on
+    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+    RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
+  </Directory>
+
+  SSLEngine on
+  # This cert is bundled by default in Ubuntu
+  SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
+  SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
+
+  <FilesMatch "\.(cgi|shtml|phtml|php)$">
+    SSLOptions +StdEnvVars
+  </FilesMatch>
+
+  BrowserMatch "MSIE [2-6]" \
+  nokeepalive ssl-unclean-shutdown \
+  downgrade-1.0 force-response-1.0
+  BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
+
+</VirtualHost>
+
+</IfModule>
diff --git a/php/phpxmlrpc/tests/ci/config/codecoverage_xdebug.ini b/php/phpxmlrpc/tests/ci/config/codecoverage_xdebug.ini
new file mode 100644 (file)
index 0000000..7891eb0
--- /dev/null
@@ -0,0 +1,10 @@
+# php.ini settings used to enable code coverage with xdebug
+
+zend_extension=xdebug.so
+
+# xdebug 3
+xdebug.mode=coverage
+# xdebug 2
+xdebug.coverage_enable=1
+
+memory_limit = -1
diff --git a/php/phpxmlrpc/tests/ci/docker/entrypoint.sh b/php/phpxmlrpc/tests/ci/docker/entrypoint.sh
new file mode 100644 (file)
index 0000000..e3565ab
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+USERNAME="${1:-docker}"
+
+echo "[$(date)] Bootstrapping the Test container..."
+
+clean_up() {
+    # Perform program exit housekeeping
+
+    echo "[$(date)] Stopping the Web server"
+    service apache2 stop
+
+    echo "[$(date)] Stopping Privoxy"
+    service privoxy stop
+
+    echo "[$(date)] Stopping FPM"
+    service php-fpm stop
+
+    echo "[$(date)] Exiting"
+    exit
+}
+
+# Fix UID & GID for user
+
+echo "[$(date)] Fixing filesystem permissions..."
+
+ORIGPASSWD=$(cat /etc/passwd | grep "^${USERNAME}:")
+ORIG_UID=$(echo "$ORIGPASSWD" | cut -f3 -d:)
+ORIG_GID=$(echo "$ORIGPASSWD" | cut -f4 -d:)
+CONTAINER_USER_HOME=$(echo "$ORIGPASSWD" | cut -f6 -d:)
+CONTAINER_USER_UID=${CONTAINER_USER_UID:=$ORIG_UID}
+CONTAINER_USER_GID=${CONTAINER_USER_GID:=$ORIG_GID}
+
+if [ "$CONTAINER_USER_UID" != "$ORIG_UID" -o "$CONTAINER_USER_GID" != "$ORIG_GID" ]; then
+    groupmod -g "$CONTAINER_USER_GID" "${USERNAME}"
+    usermod -u "$CONTAINER_USER_UID" -g "$CONTAINER_USER_GID" "${USERNAME}"
+fi
+if [ $(stat -c '%u' "${CONTAINER_USER_HOME}") != "${CONTAINER_USER_UID}" -o $(stat -c '%g' "${CONTAINER_USER_HOME}") != "${CONTAINER_USER_GID}" ]; then
+    chown "${CONTAINER_USER_UID}":"${CONTAINER_USER_GID}" "${CONTAINER_USER_HOME}"
+    chown -R "${CONTAINER_USER_UID}":"${CONTAINER_USER_GID}" "${CONTAINER_USER_HOME}"/.*
+fi
+# @todo do the same chmod for ${TESTS_ROOT_DIR}, if it's not within CONTAINER_USER_HOME
+
+echo "[$(date)] Fixing Apache configuration..."
+
+sed -e "s?^export TESTS_ROOT_DIR=.*?export TESTS_ROOT_DIR=${TESTS_ROOT_DIR}?g" --in-place /etc/apache2/envvars
+sed -e "s?^export APACHE_RUN_USER=.*?export APACHE_RUN_USER=${USERNAME}?g" --in-place /etc/apache2/envvars
+sed -e "s?^export APACHE_RUN_GROUP=.*?export APACHE_RUN_GROUP=${USERNAME}?g" --in-place /etc/apache2/envvars
+
+echo "[$(date)] Fixing FPM configuration..."
+
+FPMCONF="/etc/php/$(php -r 'echo implode(".",array_slice(explode(".",PHP_VERSION),0,2));' 2>/dev/null)/fpm/pool.d/www.conf"
+sed -e "s?^user =.*?user = ${USERNAME}?g" --in-place "${FPMCONF}"
+sed -e "s?^group =.*?group = ${USERNAME}?g" --in-place "${FPMCONF}"
+sed -e "s?^listen.owner =.*?listen.owner = ${USERNAME}?g" --in-place "${FPMCONF}"
+sed -e "s?^listen.group =.*?listen.group = ${USERNAME}?g" --in-place "${FPMCONF}"
+
+echo "[$(date)] Running Composer..."
+
+# @todo if there is a composer.lock file present, there are chances it might be a leftover from when running the
+#       container using a different php version. We should then back it up / do some symlink magic to make sure that
+#       it matches the current php version and a hash of composer.json...
+sudo "${USERNAME}" -c "cd ${TESTS_ROOT_DIR} && composer install"
+
+trap clean_up TERM
+
+echo "[$(date)] Starting FPM..."
+service php-fpm start
+
+echo "[$(date)] Starting the Web server..."
+service apache2 start
+
+echo "[$(date)] Starting Privoxy..."
+service privoxy start
+
+tail -f /dev/null &
+child=$!
+wait "$child"
diff --git a/php/phpxmlrpc/tests/ci/setup/create_user.sh b/php/phpxmlrpc/tests/ci/setup/create_user.sh
new file mode 100755 (executable)
index 0000000..33553d2
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# @todo make the GID & UID of the user variable (we picked 2000 as it is the one used by default by Travis)
+
+set -e
+
+USERNAME="${1:-docker}"
+
+addgroup --gid 2000 "${USERNAME}"
+adduser --system --uid=2000 --gid=2000 --home "/home/${USERNAME}" --shell /bin/bash "${USERNAME}"
+adduser "${USERNAME}" "${USERNAME}"
+
+mkdir -p "/home/${USERNAME}/.ssh"
+cp /etc/skel/.[!.]* "/home/${USERNAME}"
+
+chown -R "${USERNAME}:${USERNAME}" "/home/${USERNAME}"
+
+if [ -f /etc/sudoers ]; then
+    adduser "${USERNAME}" sudo
+    sed -i "\$ a ${USERNAME}   ALL=\(ALL:ALL\) NOPASSWD: ALL" /etc/sudoers
+fi
diff --git a/php/phpxmlrpc/tests/ci/setup/install_packages.sh b/php/phpxmlrpc/tests/ci/setup/install_packages.sh
new file mode 100755 (executable)
index 0000000..b22f398
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Has to be run as admin
+
+set -e
+
+DEBIAN_FRONTEND=noninteractive apt-get install -y \
+    lsb-release sudo wget
diff --git a/php/phpxmlrpc/tests/ci/setup/setup_apache.sh b/php/phpxmlrpc/tests/ci/setup/setup_apache.sh
new file mode 100755 (executable)
index 0000000..b29cde2
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# Install and configure apache2
+# Has to be run as admin
+# @todo make this work across all ubuntu versions (precise to focal)
+
+set -e
+
+SCRIPT_DIR="$(dirname -- "$(readlink -f "$0")")"
+
+DEBIAN_FRONTEND=noninteractive apt-get install -y apache2
+
+# set up Apache for php-fpm
+
+a2enmod rewrite proxy_fcgi setenvif ssl
+
+# in case mod-php was enabled (this is the case at least on GHA's ubuntu with php 5.x and shivammathur/setup-php)
+# @todo silence errors in a smarter way
+rm /etc/apache2/mods-enabled/php* || true
+
+# configure apache virtual hosts
+
+cp -f "$SCRIPT_DIR/../config/apache_vhost" /etc/apache2/sites-available/000-default.conf
+
+# default apache siteaccess found in GHA Ubuntu. We remove it just in case
+if [ -f /etc/apache2/sites-available/default-ssl.conf ]; then
+    rm /etc/apache2/sites-available/default-ssl.conf
+fi
+
+if [ -n "${GITHUB_ACTIONS}" ]; then
+    echo "export TESTS_ROOT_DIR=$(pwd)" >> /etc/apache2/envvars
+else
+    echo "export TESTS_ROOT_DIR=/var/www/html" >> /etc/apache2/envvars
+fi
+echo "export HTTPSERVER=localhost" >> /etc/apache2/envvars
+
+service apache2 restart
diff --git a/php/phpxmlrpc/tests/ci/setup/setup_code_coverage.sh b/php/phpxmlrpc/tests/ci/setup/setup_code_coverage.sh
new file mode 100755 (executable)
index 0000000..49e7af8
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# @todo add 'query' action
+# @todo avoid reloading php-fpm if config did not change
+
+# Note: we have php set up either via Ubuntu packages (PHP_VERSION=default) or Sury packages.
+#       xdebug comes either at version 2 or 3
+
+set -e
+
+PHPCONFDIR_CLI=$(php -i | grep 'Scan this dir for additional .ini files' | sed 's|Scan this dir for additional .ini files => ||')
+PHPCONFDIR_FPM=$(echo "$PHPCONFDIR_CLI" | sed 's|/cli/|/fpm/|')
+
+enable_cc() {
+    if [ -L "${PHPCONFDIR_CLI}/99-codecoverage_xdebug.ini" ]; then sudo rm "${PHPCONFDIR_CLI}/99-codecoverage_xdebug.ini"; fi
+    sudo ln -s $(realpath tests/ci/config/codecoverage_xdebug.ini) "${PHPCONFDIR_CLI}/99-codecoverage_xdebug.ini"
+    if [ -L "${PHPCONFDIR_FPM}/99-codecoverage_xdebug.ini" ]; then sudo rm "${PHPCONFDIR_FPM}/99-codecoverage_xdebug.ini"; fi
+    sudo ln -s $(realpath tests/ci/config/codecoverage_xdebug.ini) "${PHPCONFDIR_FPM}/99-codecoverage_xdebug.ini"
+
+    sudo service php-fpm restart
+}
+
+disable_cc() {
+    if [ -L "${PHPCONFDIR_CLI}/99-codecoverage_xdebug.ini" ]; then sudo rm "${PHPCONFDIR_CLI}/99-codecoverage_xdebug.ini"; fi
+    if [ -L "${PHPCONFDIR_FPM}/99-codecoverage_xdebug.ini" ]; then sudo rm "${PHPCONFDIR_FPM}/99-codecoverage_xdebug.ini"; fi
+
+    sudo service php-fpm restart
+}
+
+case "$1" in
+   enable | on)
+       enable_cc
+       ;;
+   disable | off)
+       disable_cc
+       ;;
+   *)
+       echo "ERROR: unknown action '${1}', please use 'enable' or 'disable'" >&2
+       exit 1
+       ;;
+esac
diff --git a/php/phpxmlrpc/tests/ci/setup/setup_composer.sh b/php/phpxmlrpc/tests/ci/setup/setup_composer.sh
new file mode 100755 (executable)
index 0000000..cd2f81c
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# Installs Composer (latest version, to avoid relying on old ones bundled with the OS)
+# @todo allow users to lock down to Composer v1 if needed
+
+if dpkg -l composer 2>/dev/null; then
+    apt-get remove -y composer
+fi
+
+### Code below taken from https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md
+
+EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
+php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
+
+if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
+then
+    >&2 echo 'ERROR: Invalid installer signature'
+    rm composer-setup.php
+    exit 1
+fi
+
+php composer-setup.php --quiet --install-dir=/usr/local/bin
+RESULT=$?
+rm composer-setup.php
+
+###
+
+if [ -f /usr/local/bin/composer.phar -a "$RESULT" = 0 ]; then
+    mv /usr/local/bin/composer.phar /usr/local/bin/composer && chmod 755 /usr/local/bin/composer
+fi
+
+exit $RESULT
diff --git a/php/phpxmlrpc/tests/ci/setup/setup_perl.sh b/php/phpxmlrpc/tests/ci/setup/setup_perl.sh
new file mode 100644 (file)
index 0000000..102f677
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Installs php modules necessary to test the Perl file in the extras dir
+
+# Has to be run as admin
+
+# @todo test in the VM env: do we need any ubuntu dev packages ?
+
+set -e
+
+DEBIAN_FRONTEND=noninteractive apt-get install -y \
+    libexpat1-dev
+
+yes | perl -MCPAN -e 'install XML::Parser'
+yes | perl -MCPAN -e 'install Frontier::Client'
diff --git a/php/phpxmlrpc/tests/ci/setup/setup_php.sh b/php/phpxmlrpc/tests/ci/setup/setup_php.sh
new file mode 100755 (executable)
index 0000000..038de3d
--- /dev/null
@@ -0,0 +1,88 @@
+#!/bin/sh
+
+# Has to be run as admin
+
+# @todo make it optional to install xdebug. It is fe. missing in sury's ppa for Xenial
+# @todo make it optional to install fpm. It is not needed for the cd workflow
+# @todo make it optional to disable xdebug ?
+
+set -e
+
+configure_php_ini() {
+    # note: these settings are not required for cli config
+    echo "cgi.fix_pathinfo = 1" >> "${1}"
+    echo "always_populate_raw_post_data = -1" >> "${1}"
+
+    # we disable xdebug for speed for both cli and web mode
+    phpdismod xdebug
+}
+
+# install php
+PHP_VERSION="$1"
+DEBIAN_VERSION="$(lsb_release -s -c)"
+
+if [ "${PHP_VERSION}" = default ]; then
+    if [ "${DEBIAN_VERSION}" = jessie -o "${DEBIAN_VERSION}" = precise -o "${DEBIAN_VERSION}" = trusty ]; then
+        PHPSUFFIX=5
+    else
+        PHPSUFFIX=
+    fi
+    # @todo check for mbstring presence in php5 (jessie) packages
+    DEBIAN_FRONTEND=noninteractive apt-get install -y \
+        php${PHPSUFFIX} \
+        php${PHPSUFFIX}-cli \
+        php${PHPSUFFIX}-dom \
+        php${PHPSUFFIX}-curl \
+        php${PHPSUFFIX}-fpm \
+        php${PHPSUFFIX}-mbstring \
+        php${PHPSUFFIX}-xdebug
+else
+    # on GHA runners ubuntu version, php 7.4 and 8.0 seem to be preinstalled. Remove them if found
+    for PHP_CURRENT in $(dpkg -l | grep -E 'php.+-common' | awk '{print $2}'); do
+        if [ "${PHP_CURRENT}" != "php${PHP_VERSION}-common" ]; then
+            apt-get purge -y "${PHP_CURRENT}"
+        fi
+    done
+
+    DEBIAN_FRONTEND=noninteractive apt-get install -y language-pack-en-base software-properties-common
+    LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php
+    apt-get update
+
+    DEBIAN_FRONTEND=noninteractive apt-get install -y \
+        php${PHP_VERSION} \
+        php${PHP_VERSION}-cli \
+        php${PHP_VERSION}-dom \
+        php${PHP_VERSION}-curl \
+        php${PHP_VERSION}-fpm \
+        php${PHP_VERSION}-mbstring \
+        php${PHP_VERSION}-xdebug
+
+    update-alternatives --set php /usr/bin/php${PHP_VERSION}
+fi
+
+PHPVER=$(php -r 'echo implode(".",array_slice(explode(".",PHP_VERSION),0,2));' 2>/dev/null)
+
+configure_php_ini /etc/php/${PHPVER}/fpm/php.ini
+
+# use a nice name for the php-fpm service, so that it does not depend on php version running. Try to make that work
+# both for docker and VMs
+service "php${PHPVER}-fpm" stop
+if [ -f "/etc/init.d/php${PHPVER}-fpm" ]; then
+    ln -s "/etc/init.d/php${PHPVER}-fpm" /etc/init.d/php-fpm
+fi
+if [ -f "/lib/systemd/system/php${PHPVER}-fpm.service" ]; then
+    ln -s "/lib/systemd/system/php${PHPVER}-fpm.service" /lib/systemd/system/php-fpm.service
+    if [ ! -f /.dockerenv ]; then
+        systemctl daemon-reload
+    fi
+fi
+
+# @todo shall we configure php-fpm?
+
+service php-fpm start
+
+# configure apache (if installed)
+if [ -n "$(dpkg --list | grep apache)" ]; then
+    a2enconf php${PHPVER}-fpm
+    service apache2 restart
+fi
diff --git a/php/phpxmlrpc/tests/ci/setup/setup_privoxy.sh b/php/phpxmlrpc/tests/ci/setup/setup_privoxy.sh
new file mode 100755 (executable)
index 0000000..c39e62f
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# Install and configure privoxy
+# Has to be run as admin
+
+set -e
+
+SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
+
+DEBIAN_FRONTEND=noninteractive apt-get install -y privoxy
+
+cp -f "$SCRIPT_DIR/../config/privoxy" /etc/privoxy/config
+service privoxy restart
diff --git a/php/phpxmlrpc/tests/ci/travis/apache_vhost b/php/phpxmlrpc/tests/ci/travis/apache_vhost
deleted file mode 100644 (file)
index 87841d6..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-# Configuration file for Apache running on Travis.
-# PHP setup in FCGI mode
-
-<VirtualHost *:80>
-
-  DocumentRoot %TRAVIS_BUILD_DIR%
-
-  ErrorLog "%TRAVIS_BUILD_DIR%/apache_error.log"
-  CustomLog "%TRAVIS_BUILD_DIR%/apache_access.log" combined
-
-  <Directory "%TRAVIS_BUILD_DIR%">
-    Options FollowSymLinks MultiViews ExecCGI
-    AllowOverride All
-    Order deny,allow
-    Allow from all
-  </Directory>
-
-  # Wire up Apache to use Travis CI's php-fpm.
-  <IfModule mod_fastcgi.c>
-    AddHandler php5-fcgi .php
-    Action php5-fcgi /php5-fcgi
-    Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
-    FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization
-  </IfModule>
-
-</VirtualHost>
-
-<IfModule mod_ssl.c>
-
-<VirtualHost _default_:443>
-
-  DocumentRoot %TRAVIS_BUILD_DIR%
-
-  ErrorLog "%TRAVIS_BUILD_DIR%/apache_error.log"
-  CustomLog "%TRAVIS_BUILD_DIR%/apache_access.log" combined
-
-  <Directory "%TRAVIS_BUILD_DIR%">
-    Options FollowSymLinks MultiViews ExecCGI
-    AllowOverride All
-    Order deny,allow
-    Allow from all
-  </Directory>
-
-  # Wire up Apache to use Travis CI's php-fpm.
-  <IfModule mod_fastcgi.c>
-    AddHandler php5-fcgi .php
-    Action php5-fcgi /php5-fcgi
-    Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
-    #FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization
-  </IfModule>
-
-  SSLEngine on
-  # This cert is bundled by default in Ubuntu
-  SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
-  SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
-
-  <FilesMatch "\.(cgi|shtml|phtml|php)$">
-    SSLOptions +StdEnvVars
-  </FilesMatch>
-
-  BrowserMatch "MSIE [2-6]" \
-  nokeepalive ssl-unclean-shutdown \
-  downgrade-1.0 force-response-1.0
-  BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
-
-</VirtualHost>
-
-</IfModule>
diff --git a/php/phpxmlrpc/tests/ci/travis/apache_vhost_hhvm b/php/phpxmlrpc/tests/ci/travis/apache_vhost_hhvm
deleted file mode 100644 (file)
index 63e57da..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-# Configuration file for Apache running on Travis.
-# HHVM setup in FCGI mode
-
-<VirtualHost *:80>
-
-  DocumentRoot %TRAVIS_BUILD_DIR%
-
-  ErrorLog "%TRAVIS_BUILD_DIR%/apache_error.log"
-  CustomLog "%TRAVIS_BUILD_DIR%/apache_access.log" combined
-
-  <Directory "%TRAVIS_BUILD_DIR%">
-    Options FollowSymLinks MultiViews ExecCGI
-    AllowOverride All
-    Order deny,allow
-    Allow from all
-  </Directory>
-
-  # Configure Apache for HHVM FastCGI.
-  # See https://github.com/facebook/hhvm/wiki/fastcgi
-  <IfModule mod_fastcgi.c>
-    <FilesMatch \.php$>
-      SetHandler hhvm-php-extension
-    </FilesMatch>
-    Alias /hhvm /hhvm
-    Action hhvm-php-extension /hhvm virtual
-    FastCgiExternalServer /hhvm -host 127.0.0.1:9000 -pass-header Authorization -idle-timeout 300
-  </IfModule>
-
-</VirtualHost>
-
-<IfModule mod_ssl.c>
-
-<VirtualHost _default_:443>
-
-  DocumentRoot %TRAVIS_BUILD_DIR%
-
-  ErrorLog "%TRAVIS_BUILD_DIR%/apache_error.log"
-  CustomLog "%TRAVIS_BUILD_DIR%/apache_access.log" combined
-
-  <Directory "%TRAVIS_BUILD_DIR%">
-    Options FollowSymLinks MultiViews ExecCGI
-    AllowOverride All
-    Order deny,allow
-    Allow from all
-  </Directory>
-
-  # Configure Apache for HHVM FastCGI.
-  # See https://github.com/facebook/hhvm/wiki/fastcgi
-  <IfModule mod_fastcgi.c>
-    <FilesMatch \.php$>
-      SetHandler hhvm-php-extension
-    </FilesMatch>
-    Alias /hhvm /hhvm
-    Action hhvm-php-extension /hhvm virtual
-    #FastCgiExternalServer /hhvm -host 127.0.0.1:9000 -pass-header Authorization -idle-timeout 300
-  </IfModule>
-
-  SSLEngine on
-  # This cert is bundled by default in Ubuntu
-  SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
-  SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
-
-  <FilesMatch "\.(cgi|shtml|phtml|php)$">
-    SSLOptions +StdEnvVars
-  </FilesMatch>
-
-  BrowserMatch "MSIE [2-6]" \
-  nokeepalive ssl-unclean-shutdown \
-  downgrade-1.0 force-response-1.0
-  BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
-
-</VirtualHost>
-
-</IfModule>
diff --git a/php/phpxmlrpc/tests/ci/travis/setup_apache.sh b/php/phpxmlrpc/tests/ci/travis/setup_apache.sh
deleted file mode 100755 (executable)
index a39d676..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-
-# set up Apache for php-fpm
-# @see https://github.com/travis-ci/travis-ci.github.com/blob/master/docs/user/languages/php.md#apache--php
-
-sudo a2enmod rewrite actions fastcgi alias ssl
-
-# configure apache virtual hosts
-sudo cp -f tests/ci/travis/apache_vhost /etc/apache2/sites-available/default
-sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/default
-sudo service apache2 restart
diff --git a/php/phpxmlrpc/tests/ci/travis/setup_apache_hhvm.sh b/php/phpxmlrpc/tests/ci/travis/setup_apache_hhvm.sh
deleted file mode 100755 (executable)
index a72941d..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-
-# set up Apache for hhvm-fcgi
-# @see https://github.com/travis-ci/travis-ci.github.com/blob/master/docs/user/languages/php.md#apache--php
-
-sudo a2enmod rewrite actions fastcgi alias ssl
-
-# configure apache virtual hosts
-sudo cp -f tests/ci/travis/apache_vhost_hhvm /etc/apache2/sites-available/default
-sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/default
-sudo service apache2 restart
diff --git a/php/phpxmlrpc/tests/ci/travis/setup_hhvm.sh b/php/phpxmlrpc/tests/ci/travis/setup_hhvm.sh
deleted file mode 100755 (executable)
index 289e750..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-# start HHVM
-hhvm -m daemon -vServer.Type=fastcgi -vServer.Port=9000 -vServer.FixPathInfo=true
diff --git a/php/phpxmlrpc/tests/ci/travis/setup_php_fpm.sh b/php/phpxmlrpc/tests/ci/travis/setup_php_fpm.sh
deleted file mode 100755 (executable)
index dd462b2..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-
-# enable php-fpm
-sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
-# work around travis issue #3385
-if [ "$TRAVIS_PHP_VERSION" = "7.0" -a -n "$(ls -A ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d)" ]; then
-  sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf
-fi
-if [ "$TRAVIS_PHP_VERSION" = "7.1" -a -n "$(ls -A ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d)" ]; then
-  sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf
-fi
-echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
-echo "always_populate_raw_post_data = -1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
-~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm
diff --git a/php/phpxmlrpc/tests/ci/travis/setup_privoxy.sh b/php/phpxmlrpc/tests/ci/travis/setup_privoxy.sh
deleted file mode 100755 (executable)
index 12e0e61..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-# configure privoxy
-
-sudo cp -f tests/ci/travis/privoxy /etc/privoxy/config
-sudo service privoxy restart
diff --git a/php/phpxmlrpc/tests/ci/vm.sh b/php/phpxmlrpc/tests/ci/vm.sh
new file mode 100755 (executable)
index 0000000..1cef916
--- /dev/null
@@ -0,0 +1,150 @@
+#!/bin/sh
+
+# @todo support getting the 2 vars as cli options as well as via env vars?
+
+set -e
+
+ACTION="${1}"
+
+# Valid values: 'default', 5.6, 7.0 .. 7.4, 8.0 .. 8.1
+export PHP_VERSION=${PHP_VERSION:-default}
+# Valid values: precise (12), trusty (14), xenial (16), bionic (18), focal (20)
+export UBUNTU_VERSION=${UBUNTU_VERSION:-bionic}
+
+CONTAINER_USER=docker
+CONTAINER_BUILD_DIR="/home/${CONTAINER_USER}/build"
+ROOT_DIR="$(dirname -- "$(dirname -- "$(dirname -- "$(readlink -f "$0")")")")"
+IMAGE_NAME=phpxmlrpc:${UBUNTU_VERSION}-${PHP_VERSION}
+CONTAINER_NAME=phpxmlrpc_${UBUNTU_VERSION}_${PHP_VERSION}
+
+cd "$(dirname -- "$(readlink -f "$0")")"
+
+help() {
+printf "Usage: vm.sh [OPTIONS] ACTION [OPTARGS]
+
+Manages the Test Environment Docker Stack
+
+Commands:
+    build             build or rebuild the containers and set up the test env
+    cleanup           removes the docker containers and their images
+    enter             enter the test container
+    inspect
+    logs
+    ps
+    runtests [\$suite] execute the test suite using the test container (or a single test scenario eg. tests/1ParsingBugsTest.php)
+    runcoverage       execute the test suite and generate a code coverage report (in build/coverage)
+    start             start the containers
+    stop              stop containers
+    top
+
+Options:
+    -h                print help
+
+Environment variables: to be set before the 'build' action
+    PHP_VERSION       default value: 'default', ie. the stock php version from the Ubuntu version in use. Other possible values: 5.6, 7.0 .. 8.1
+    UBUNTU_VERSION    default value: bionic. Other possible values: xenial, focal
+"
+}
+
+build() {
+    stop
+    docker build --build-arg PHP_VERSION --build-arg UBUNTU_VERSION -t "${IMAGE_NAME}" .
+    if docker inspect "${CONTAINER_NAME}" >/dev/null 2>/dev/null; then
+        docker rm "${CONTAINER_NAME}"
+    fi
+    docker run -d \
+        -p 80:80 -p 443:443 -p 8080:8080 \
+        --name "${CONTAINER_NAME}" \
+        --env CONTAINER_USER_UID=$(id -u) --env CONTAINER_USER_GID=$(id -g) \
+        --env TESTS_ROOT_DIR=${CONTAINER_BUILD_DIR} \
+        --env HTTPSERVER=localhost \
+        --env HTTPURI=/demo/server/server.php \
+        --env HTTPSSERVER=localhost \
+        --env HTTPSURI=/demo/server/server.php \
+        --env PROXYSERVER=localhost:8080 \
+        --env HTTPSVERIFYHOST=0 \
+        --env HTTPSIGNOREPEER=1 \
+        --env SSLVERSION=0 \
+        --env DEBUG=0 \
+        -v "${ROOT_DIR}":"${CONTAINER_BUILD_DIR}" \
+         "${IMAGE_NAME}"
+}
+
+start() {
+    if docker inspect "${CONTAINER_NAME}" >/dev/null 2>/dev/null; then
+        docker start "${CONTAINER_NAME}"
+    else
+        build
+    fi
+}
+
+stop() {
+    if docker inspect "${CONTAINER_NAME}" >/dev/null 2>/dev/null; then
+        docker stop "${CONTAINER_NAME}"
+    fi
+}
+
+case "${ACTION}" in
+
+    build)
+        build
+        stop
+        ;;
+
+    cleanup)
+        docker rm "${CONTAINER_NAME}"
+        docker rmi "${IMAGE_NAME}"
+        ;;
+
+    enter | shell | cli)
+        # @todo allow login as root
+        docker exec -it "${CONTAINER_NAME}" su "${CONTAINER_USER}"
+        ;;
+
+    # @todo implement
+    #exec)
+    #    ;;
+
+    restart)
+        stop
+        start
+        ;;
+
+    runcoverage)
+        # @todo clean up /tmp/phpxmlrpc and .phpunit.result.cache
+        if [ ! -d build ]; then mkdir build; fi
+        docker exec -t "${CONTAINER_NAME}" /home/${CONTAINER_USER}/build/tests/ci/setup/setup_code_coverage.sh enable
+        docker exec -it "${CONTAINER_NAME}" su "${CONTAINER_USER}" -c "./vendor/bin/phpunit --coverage-html build/coverage -v tests"
+        docker exec -t "${CONTAINER_NAME}" /home/${CONTAINER_USER}/build/tests/ci/setup/setup_code_coverage.sh disable
+        ;;
+
+    runtests)
+        docker exec -it "${CONTAINER_NAME}" su "${CONTAINER_USER}" -c "./vendor/bin/phpunit -v tests"
+        ;;
+
+    start)
+        start
+        ;;
+
+    #status)
+    #    :
+    #    ;;
+
+    stop)
+        stop
+        ;;
+
+    ps)
+        docker ps --filter "name=${CONTAINER_NAME}"
+        ;;
+
+    diff | inspect | kill | logs | pause | port | stats | top | unpause)
+        docker container "${ACTION}" "${CONTAINER_NAME}"
+        ;;
+
+    *)
+        printf "\n\e[31mERROR:\e[0m unknown action '${ACTION}'\n\n" >&2
+        help
+        exit 1
+        ;;
+esac
index 6660b9e..712fa99 100644 (file)
@@ -4,47 +4,50 @@
  * Common parameter parsing for benchmark and tests scripts.
  *
  * @param integer DEBUG
- * @param string  LOCALSERVER
- * @param string  URI
+ * @param string  HTTPSERVER
+ * @param string  HTTPURI
  * @param string  HTTPSSERVER
- * @param string  HTTPSSURI
- * @param string  PROXY
- * @param string  NOPROXY
+ * @param string  HTTPSURI
  * @param bool    HTTPSIGNOREPEER
  * @param int     HTTPSVERIFYHOST
  * @param int     SSLVERSION
+ * @param string  PROXYSERVER
  *
- * @copyright (C) 2007-2015 G. Giunta
+ * @copyright (C) 2007-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
+ *
+ * @todo rename both the class and the file. PhpXmlRpc_TestArgParser ?
  **/
 class argParser
 {
+    /**
+     * @return array
+     */
     public static function getArgs()
     {
-        global $argv;
-
+        /// @todo should we prefix all test parameters with TESTS_ ?
         $args = array(
             'DEBUG' => 0,
-            'LOCALSERVER' => 'localhost',
-            'HTTPSSERVER' => 'gggeek.ssl.altervista.org',
-            'HTTPSURI' => '/sw/xmlrpc/demo/server/server.php',
+            'HTTPSERVER' => 'localhost',
+            'HTTPURI' => null,
+            // now that we run tests in Docker by default, with a webserver set up for https, let's default to it
+            'HTTPSSERVER' => 'localhost',
+            'HTTPSURI' => null,
+            // example alternative:
+            //'HTTPSSERVER' => 'gggeek.altervista.org',
+            //'HTTPSURI' => '/sw/xmlrpc/demo/server/server.php',
             'HTTPSIGNOREPEER' => false,
             'HTTPSVERIFYHOST' => 2,
             'SSLVERSION' => 0,
             'PROXYSERVER' => null,
-            'NOPROXY' => false,
-            'LOCALPATH' => __DIR__,
+            //'LOCALPATH' => __DIR__,
         );
 
-        // check for command line vs web page input params
+        // check for command line (env vars) vs. web page input params
         if (!isset($_SERVER['REQUEST_METHOD'])) {
-            if (isset($argv)) {
-                foreach ($argv as $param) {
-                    $param = explode('=', $param);
-                    if (count($param) > 1) {
-                        $pname = strtoupper(ltrim($param[0], '-'));
-                        $$pname = $param[1];
-                    }
+            foreach($_SERVER as $key => $val) {
+                if (array_key_exists($key, $args)) {
+                    $$key = $val;
                 }
             }
         } else {
@@ -56,32 +59,64 @@ class argParser
         if (isset($DEBUG)) {
             $args['DEBUG'] = intval($DEBUG);
         }
-        if (isset($LOCALSERVER)) {
-            $args['LOCALSERVER'] = $LOCALSERVER;
+
+        if (isset($HTTPSERVER)) {
+            $args['HTTPSERVER'] = $HTTPSERVER;
         } else {
             if (isset($HTTP_HOST)) {
-                $args['LOCALSERVER'] = $HTTP_HOST;
+                $args['HTTPSERVER'] = $HTTP_HOST;
             } elseif (isset($_SERVER['HTTP_HOST'])) {
-                $args['LOCALSERVER'] = $_SERVER['HTTP_HOST'];
+                $args['HTTPSERVER'] = $_SERVER['HTTP_HOST'];
             }
         }
+
+        if (!isset($HTTPURI) || $HTTPURI == '') {
+            // GUESTIMATE the url of local demo server
+            // play nice to php 4 and 5 in retrieving URL of server.php
+            /// @todo filter out query string from REQUEST_URI
+            /// @todo review this code...
+            if (isset($REQUEST_URI)) {
+                $HTTPURI = str_replace('/tests/testsuite.php', '/demo/server/server.php', $REQUEST_URI);
+                $HTTPURI = str_replace('/testsuite.php', '/server.php', $HTTPURI);
+                $HTTPURI = str_replace('/extras/benchmark.php', '/demo/server/server.php', $HTTPURI);
+                $HTTPURI = str_replace('/benchmark.php', '/server.php', $HTTPURI);
+            } elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['REQUEST_METHOD'])) {
+                $HTTPURI = str_replace('/tests/testsuite.php', '/demo/server/server.php', $_SERVER['PHP_SELF']);
+                $HTTPURI = str_replace('/testsuite.php', '/server.php', $HTTPURI);
+                $HTTPURI = str_replace('/extras/benchmark.php', '/demo/server/server.php', $HTTPURI);
+                $HTTPURI = str_replace('/benchmark.php', '/server.php', $HTTPURI);
+            } else {
+                $HTTPURI = '/demo/server/server.php';
+            }
+        }
+        if ($HTTPURI[0] != '/') {
+            $HTTPURI = '/' . $HTTPURI;
+        }
+        $args['HTTPURI'] = $HTTPURI;
+
         if (isset($HTTPSSERVER)) {
             $args['HTTPSSERVER'] = $HTTPSSERVER;
         }
+
+        /// @todo if $HTTPSURI is unset, and HTTPSSERVER == localhost, use HTTPURI
         if (isset($HTTPSURI)) {
             $args['HTTPSURI'] = $HTTPSURI;
         }
+
         if (isset($HTTPSIGNOREPEER)) {
             $args['HTTPSIGNOREPEER'] = (bool)$HTTPSIGNOREPEER;
         }
+
         if (isset($HTTPSVERIFYHOST)) {
             $args['HTTPSVERIFYHOST'] = (int)$HTTPSVERIFYHOST;
         }
+
         if (isset($SSLVERSION)) {
             $args['SSLVERSION'] = (int)$SSLVERSION;
         }
-        if (isset($PROXY)) {
-            $arr = explode(':', $PROXY);
+
+        if (isset($PROXYSERVER)) {
+            $arr = explode(':', $PROXYSERVER);
             $args['PROXYSERVER'] = $arr[0];
             if (count($arr) > 1) {
                 $args['PROXYPORT'] = $arr[1];
@@ -89,35 +124,10 @@ class argParser
                 $args['PROXYPORT'] = 8080;
             }
         }
-        // used to silence testsuite warnings about proxy code not being tested
-        if (isset($NOPROXY)) {
-            $args['NOPROXY'] = true;
-        }
-        if (!isset($URI)) {
-            // GUESTIMATE the url of local demo server
-            // play nice to php 3 and 4-5 in retrieving URL of server.php
-            /// @todo filter out query string from REQUEST_URI
-            if (isset($REQUEST_URI)) {
-                $URI = str_replace('/tests/testsuite.php', '/demo/server/server.php', $REQUEST_URI);
-                $URI = str_replace('/testsuite.php', '/server.php', $URI);
-                $URI = str_replace('/tests/benchmark.php', '/demo/server/server.php', $URI);
-                $URI = str_replace('/benchmark.php', '/server.php', $URI);
-            } elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['REQUEST_METHOD'])) {
-                $URI = str_replace('/tests/testsuite.php', '/demo/server/server.php', $_SERVER['PHP_SELF']);
-                $URI = str_replace('/testsuite.php', '/server.php', $URI);
-                $URI = str_replace('/tests/benchmark.php', '/demo/server/server.php', $URI);
-                $URI = str_replace('/benchmark.php', '/server.php', $URI);
-            } else {
-                $URI = '/demo/server/server.php';
-            }
-        }
-        if ($URI[0] != '/') {
-            $URI = '/' . $URI;
-        }
-        $args['URI'] = $URI;
-        if (isset($LOCALPATH)) {
-            $args['LOCALPATH'] = $LOCALPATH;
-        }
+
+        //if (isset($LOCALPATH)) {
+        //    $args['LOCALPATH'] = $LOCALPATH;
+        //}
 
         return $args;
     }
index ae7b998..eafe0e8 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * Used to serve back the server-side code coverage results to phpunit-selenium
  *
- * @copyright (C) 2007-2015 G. Giunta
+ * @copyright (C) 2007-2021 G. Giunta
  * @license code licensed under the BSD License: see file license.txt
  **/
 
@@ -11,6 +11,10 @@ $coverageFile = realpath(__DIR__ . "/../vendor/phpunit/phpunit-selenium/PHPUnit/
 // has to be the same value as used in server.php
 $GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = '/tmp/phpxmlrpc_coverage';
 
+if (!is_dir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'])) {
+    mkdir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']);
+}
+
 chdir($GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY']);
 
-include_once $coverageFile;
\ No newline at end of file
+include_once $coverageFile;