diff --git a/composer.json b/composer.json index 7588f7da49c..d5e2d7f411e 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "mobiledetect/mobiledetectlib": "2.8.3", "phpoffice/phpexcel": "1.8.0", "restler/framework": "^3.0", - "tecnick.com/tcpdf": "6.2.6" + "tecnick.com/tcpdf": "6.2.6", + "raven/raven": "^0.12.0" }, "suggest": { "ext-mysqlnd": "To use with MySQL or MariaDB", diff --git a/composer.lock b/composer.lock index de85792664e..9e662436d4d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "ca5a74259e0d1773089838908d58cb31", + "hash": "32e1fa78cc95c32b154a38e07706874c", "packages": [ { "name": "ccampbell/chromephp", @@ -199,6 +199,60 @@ ], "time": "2014-03-02 15:22:49" }, + { + "name": "raven/raven", + "version": "0.12.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/raven-php.git", + "reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/raven-php/zipball/bd247ca2a8fd9ccfb99b60285c9b31286384a92b", + "reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.2.4" + }, + "require-dev": { + "fabpot/php-cs-fixer": "^1.8.0", + "phpunit/phpunit": "^4.6.6" + }, + "bin": [ + "bin/raven" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.12.x-dev" + } + }, + "autoload": { + "psr-0": { + "Raven_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "David Cramer", + "email": "dcramer@gmail.com" + } + ], + "description": "A PHP client for Sentry (http://getsentry.com)", + "homepage": "http://getsentry.com", + "keywords": [ + "log", + "logging" + ], + "time": "2015-05-19 20:20:08" + }, { "name": "restler/framework", "version": "3.0.0", diff --git a/htdocs/includes/composer/autoload_namespaces.php b/htdocs/includes/composer/autoload_namespaces.php index 5ff83246d01..27e1e79f483 100644 --- a/htdocs/includes/composer/autoload_namespaces.php +++ b/htdocs/includes/composer/autoload_namespaces.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname(dirname($vendorDir)); return array( + 'Raven_' => array($vendorDir . '/raven/raven/lib'), 'PHPExcel' => array($vendorDir . '/phpoffice/phpexcel/Classes'), 'Luracast\\Restler' => array($vendorDir . '/restler/framework'), 'Detection' => array($vendorDir . '/mobiledetect/mobiledetectlib/namespaced'), diff --git a/htdocs/includes/composer/installed.json b/htdocs/includes/composer/installed.json index 5b2a50000f6..f2b965e2b6e 100644 --- a/htdocs/includes/composer/installed.json +++ b/htdocs/includes/composer/installed.json @@ -340,5 +340,61 @@ "pdf417", "qrcode" ] + }, + { + "name": "raven/raven", + "version": "0.12.0", + "version_normalized": "0.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/raven-php.git", + "reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/raven-php/zipball/bd247ca2a8fd9ccfb99b60285c9b31286384a92b", + "reference": "bd247ca2a8fd9ccfb99b60285c9b31286384a92b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.2.4" + }, + "require-dev": { + "fabpot/php-cs-fixer": "^1.8.0", + "phpunit/phpunit": "^4.6.6" + }, + "time": "2015-05-19 20:20:08", + "bin": [ + "bin/raven" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.12.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Raven_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "David Cramer", + "email": "dcramer@gmail.com" + } + ], + "description": "A PHP client for Sentry (http://getsentry.com)", + "homepage": "http://getsentry.com", + "keywords": [ + "log", + "logging" + ] } ] diff --git a/htdocs/includes/raven/raven/.gitignore b/htdocs/includes/raven/raven/.gitignore new file mode 100644 index 00000000000..9cfdae25943 --- /dev/null +++ b/htdocs/includes/raven/raven/.gitignore @@ -0,0 +1,5 @@ +*.lock +package.xml +/vendor +.idea +.php_cs.cache diff --git a/htdocs/includes/raven/raven/.php_cs b/htdocs/includes/raven/raven/.php_cs new file mode 100644 index 00000000000..790c396bf0d --- /dev/null +++ b/htdocs/includes/raven/raven/.php_cs @@ -0,0 +1,12 @@ +in(__DIR__) +; + +return Symfony\CS\Config\Config::create() + ->setUsingCache(true) + ->setUsingLinter(true) + ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->finder($finder) +; diff --git a/htdocs/includes/raven/raven/.travis.yml b/htdocs/includes/raven/raven/.travis.yml new file mode 100644 index 00000000000..8182db27174 --- /dev/null +++ b/htdocs/includes/raven/raven/.travis.yml @@ -0,0 +1,30 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +matrix: + allow_failures: + - php: hhvm + - php: 7.0 + fast_finish: true + +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: travis_retry composer install --no-interaction --prefer-source + +script: + - vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run + - vendor/bin/phpunit diff --git a/htdocs/includes/raven/raven/AUTHORS b/htdocs/includes/raven/raven/AUTHORS new file mode 100644 index 00000000000..b9769880bbd --- /dev/null +++ b/htdocs/includes/raven/raven/AUTHORS @@ -0,0 +1,4 @@ +The Raven PHP client was originally written by Michael van Tellingen +and is maintained by the Sentry Team. + +http://github.com/getsentry/raven-php/contributors \ No newline at end of file diff --git a/htdocs/includes/raven/raven/CHANGES b/htdocs/includes/raven/raven/CHANGES new file mode 100644 index 00000000000..6d52a4bfd49 --- /dev/null +++ b/htdocs/includes/raven/raven/CHANGES @@ -0,0 +1,39 @@ +0.12.0 +------ + +- Bumped protocol version to 6. +- Fixed an issue with the async curl handler (GH-216). +- Removed UDP transport. + +0.11.0 +------ + +- New configuration parameter: 'release' +- New configuration parameter: 'message_limit' +- New configuration parameter: 'curl_ssl_version' +- New configuration parameter: 'curl_ipv4' +- New configuration parameter: 'verify_ssl' +- Updated remote endpoint to use modern project-based path. +- Expanded default sanitizer support to include 'auth_pw' attribute. + +0.10.0 +------ + +- Added a default certificate bundle which includes common root CA's + as well as getsentry.com's CA. + +0.9.1 +----- + +- Change default curl connection to 'sync' +- Improve CLI reporting + + +0.9.0 +----- + +- Protocol version 5 +- Default to asynchronous HTTP handler using curl_multi. + + +(For previous versions see the commit history) diff --git a/htdocs/includes/raven/raven/LICENSE b/htdocs/includes/raven/raven/LICENSE new file mode 100644 index 00000000000..c53c66caf22 --- /dev/null +++ b/htdocs/includes/raven/raven/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2012 Sentry Team and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the Raven nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/htdocs/includes/raven/raven/Makefile b/htdocs/includes/raven/raven/Makefile new file mode 100644 index 00000000000..5e574ece113 --- /dev/null +++ b/htdocs/includes/raven/raven/Makefile @@ -0,0 +1,17 @@ +.PHONY: test + +develop: + composer install --dev + make setup-git + +cs: + vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff + +cs-dry-run: + vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run + +test: + vendor/bin/phpunit + +setup-git: + git config branch.autosetuprebase always diff --git a/htdocs/includes/raven/raven/README.rst b/htdocs/includes/raven/raven/README.rst new file mode 100644 index 00000000000..dec8319799f --- /dev/null +++ b/htdocs/includes/raven/raven/README.rst @@ -0,0 +1,273 @@ +raven-php +========= + +.. image:: https://secure.travis-ci.org/getsentry/raven-php.png?branch=master + :target: http://travis-ci.org/getsentry/raven-php + + +raven-php is a PHP client for `Sentry `_. + +.. code-block:: php + + // Instantiate a new client with a compatible DSN + $client = new Raven_Client('http://public:secret@example.com/1'); + + // Capture a message + $event_id = $client->getIdent($client->captureMessage('my log message')); + if ($client->getLastError() !== null) { + printf('There was an error sending the event to Sentry: %s', $client->getLastError()); + } + + // Capture an exception + $event_id = $client->getIdent($client->captureException($ex)); + + // Provide some additional data with an exception + $event_id = $client->getIdent($client->captureException($ex, array( + 'extra' => array( + 'php_version' => phpversion() + ), + ))); + + // Give the user feedback + echo "Sorry, there was an error!"; + echo "Your reference ID is " . $event_id; + + // Install error handlers and shutdown function to catch fatal errors + $error_handler = new Raven_ErrorHandler($client); + $error_handler->registerExceptionHandler(); + $error_handler->registerErrorHandler(); + $error_handler->registerShutdownFunction(); + +Installation +------------ + +Install with Composer +~~~~~~~~~~~~~~~~~~~~~ + +If you're using `Composer `_ to manage +dependencies, you can add Raven with it. + +:: + + $ composer require raven/raven:$VERSION + +(replace ``$VERSION`` with one of the available versions on `Packagist `_) +or to get the latest version off the master branch: + +:: + + $ composer require raven/raven:dev-master + +Note that using unstable versions is not recommended and should be avoided. Also +you should define a maximum version, e.g. by doing ``>=0.6,<1.0`` or ``~0.6``. + +Alternatively, use the ``^`` operator for specifying a version, e.g., + +:: + + $ composer require raven/raven:^0.11.0 + +Composer will take care of the autoloading for you, so if you require the +``vendor/autoload.php``, you're good to go. + + +Install source from GitHub +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To install the source code: + +:: + + $ git clone git://github.com/getsentry/raven-php.git + +And including it using the autoloader: + +.. code-block:: php + + require_once '/path/to/Raven/library/Raven/Autoloader.php'; + Raven_Autoloader::register(); + +Testing Your Connection +----------------------- + +The PHP client includes a simple helper script to test your connection and credentials with +the Sentry master server: + +.. code-block:: bash + + $ bin/raven test https://public:secret@app.getsentry.com/1 + Client configuration: + -> servers: [https://sentry.example.com/api/store/] + -> project: 1 + -> public_key: public + -> secret_key: secret + + Sending a test event: + -> event ID: f1765c9aed4f4ceebe5a93df9eb2d34f + + Done! + +.. note:: The CLI enforces the synchronous option on HTTP requests whereas the default configuration is asyncrhonous. + +Configuration +------------- + +Several options exist that allow you to configure the behavior of the ``Raven_Client``. These are passed as the +second parameter of the constructor, and is expected to be an array of key value pairs: + +.. code-block:: php + + $client = new Raven_Client($dsn, array( + 'option_name' => 'value', + )); + +``name`` +~~~~~~~~ + +A string to override the default value for the server's hostname. + +Defaults to ``Raven_Compat::gethostname()``. + +``tags`` +~~~~~~~~ + +An array of tags to apply to events in this context. + +.. code-block:: php + + 'tags' => array( + 'php_version' => phpversion(), + ) + + +``curl_method`` +~~~~~~~~~~~~~~~ + +Defaults to 'sync'. + +Available methods: + +- sync (default): send requests immediately when they're made +- async: uses a curl_multi handler for best-effort asynchronous submissions +- exec: asynchronously send events by forking a curl process for each item + +``curl_path`` +~~~~~~~~~~~~~ + +Defaults to 'curl'. + +Specify the path to the curl binary to be used with the 'exec' curl method. + + +``trace`` +~~~~~~~~~ + +Set this to ``false`` to disable reflection tracing (function calling arguments) in stacktraces. + + +``logger`` +~~~~~~~~~~ + +Adjust the default logger name for messages. + +Defaults to ``php``. + +``ca_cert`` +~~~~~~~~~~~ + +The path to the CA certificate bundle. + +Defaults to the common bundle which includes getsentry.com: ./data/cacert.pem + +Caveats: + +- The CA bundle is ignored unless curl throws an error suggesting it needs a cert. +- The option is only currently used within the synchronous curl transport. + +``curl_ssl_version`` +~~~~~~~~~~~~~~~~~~~~ + +The SSL version (2 or 3) to use. +By default PHP will try to determine this itself, although in some cases this must be set manually. + +``message_limit`` +~~~~~~~~~~~~~~~~~ + +Defaults to 1024 characters. + +This value is used to truncate message and frame variables. However it is not guarantee that length of whole message will be restricted by this value. + +``processors`` +~~~~~~~~~~~~~~~~~ + +An array of classes to use to process data before it is sent to Sentry. By default, Raven_SanitizeDataProcessor is used + +``processorOptions`` +~~~~~~~~~~~~~~~~~ +Options that will be passed on to a setProcessorOptions() function in a Raven_Processor sub-class before that Processor is added to the list of processors used by Raven_Client + +An example of overriding the regular expressions in Raven_SanitizeDataProcessor is below: + +.. code-block:: php + + 'processorOptions' => array( + 'Raven_SanitizeDataProcessor' => array( + 'fields_re' => '/(user_password|user_token|user_secret)/i', + 'values_re' => '/^(?:\d[ -]*?){15,16}$/' + ) + ) + +Providing Request Context +------------------------- + +Most of the time you're not actually calling out to Raven directly, but you still want to provide some additional context. This lifecycle generally constists of something like the following: + +- Set some context via a middleware (e.g. the logged in user) +- Send all given context with any events during the request lifecycle +- Cleanup context + +There are three primary methods for providing request context: + +.. code-block:: php + + // bind the logged in user + $client->user_context(array('email' => 'foo@example.com')); + + // tag the request with something interesting + $client->tags_context(array('interesting' => 'yes')); + + // provide a bit of additional context + $client->extra_context(array('happiness' => 'very')); + + +If you're performing additional requests during the lifecycle, you'll also need to ensure you cleanup the context (to reset its state): + +.. code-block:: php + + $client->context->clear(); + + +Contributing +------------ + +First, make sure you can run the test suite. Install development dependencies : + +:: + + $ composer install + +You may now use phpunit : + +:: + + $ vendor/bin/phpunit + + + +Resources +--------- + +* `Bug Tracker `_ +* `Code `_ +* `Mailing List `_ +* `IRC `_ (irc.freenode.net, #sentry) diff --git a/htdocs/includes/raven/raven/bin/raven b/htdocs/includes/raven/raven/bin/raven new file mode 100755 index 00000000000..4de251918db --- /dev/null +++ b/htdocs/includes/raven/raven/bin/raven @@ -0,0 +1,88 @@ +#!/usr/bin/php +getMessage()); + } + + $client = new Raven_Client($dsn, array( + 'trace' => true, + 'curl_method' => 'sync', + )); + + $config = get_object_vars($client); + $required_keys = array('servers', 'project', 'public_key', 'secret_key'); + + echo "Client configuration:\n"; + foreach ($required_keys as $key) { + if (empty($config[$key])) { + exit("ERROR: Missing configuration for $key"); + } + if (is_array($config[$key])) { + echo "-> $key: [".implode(", ", $config[$key])."]\n"; + } else { + echo "-> $key: $config[$key]\n"; + } + + } + echo "\n"; + + echo "Sending a test event:\n"; + + $ex = raven_cli_test("command name", array("foo" => "bar")); + $event_id = $client->captureException($ex); + + echo "-> event ID: $event_id\n"; + + $last_error = $client->getLastError(); + if (!empty($last_error)) { + exit("ERROR: There was an error sending the test event:\n " . $last_error); + } + + echo "\n"; + echo "Done!"; +} + + +function main() { + global $argv; + + $cmd = $argv[1]; + + switch ($cmd) { + case 'test': + cmd_test(@$argv[2]); + break; + default: + exit('Usage: raven test '); + } +} + +main(); diff --git a/htdocs/includes/raven/raven/composer.json b/htdocs/includes/raven/raven/composer.json new file mode 100644 index 00000000000..b8799a6c6df --- /dev/null +++ b/htdocs/includes/raven/raven/composer.json @@ -0,0 +1,35 @@ +{ + "name": "raven/raven", + "type": "library", + "description": "A PHP client for Sentry (http://getsentry.com)", + "keywords": ["log", "logging"], + "homepage": "http://getsentry.com", + "license": "BSD", + "authors": [ + { + "name": "David Cramer", + "email": "dcramer@gmail.com" + } + ], + "require-dev": { + "fabpot/php-cs-fixer": "^1.8.0", + "phpunit/phpunit": "^4.6.6" + }, + "require": { + "php": ">=5.2.4", + "ext-curl": "*" + }, + "bin": [ + "bin/raven" + ], + "autoload": { + "psr-0" : { + "Raven_" : "lib/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "0.12.x-dev" + } + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/Autoloader.php b/htdocs/includes/raven/raven/lib/Raven/Autoloader.php new file mode 100644 index 00000000000..d5adf1a58c4 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/Autoloader.php @@ -0,0 +1,43 @@ +logger = Raven_Util::get($options, 'logger', 'php'); + $this->servers = Raven_Util::get($options, 'servers'); + $this->secret_key = Raven_Util::get($options, 'secret_key'); + $this->public_key = Raven_Util::get($options, 'public_key'); + $this->project = Raven_Util::get($options, 'project', 1); + $this->auto_log_stacks = (bool) Raven_Util::get($options, 'auto_log_stacks', false); + $this->name = Raven_Util::get($options, 'name', Raven_Compat::gethostname()); + $this->site = Raven_Util::get($options, 'site', $this->_server_variable('SERVER_NAME')); + $this->tags = Raven_Util::get($options, 'tags', array()); + $this->release = Raven_util::get($options, 'release', null); + $this->trace = (bool) Raven_Util::get($options, 'trace', true); + $this->timeout = Raven_Util::get($options, 'timeout', 2); + $this->message_limit = Raven_Util::get($options, 'message_limit', self::MESSAGE_LIMIT); + $this->exclude = Raven_Util::get($options, 'exclude', array()); + $this->severity_map = null; + $this->shift_vars = (bool) Raven_Util::get($options, 'shift_vars', true); + $this->http_proxy = Raven_Util::get($options, 'http_proxy'); + $this->extra_data = Raven_Util::get($options, 'extra', array()); + $this->send_callback = Raven_Util::get($options, 'send_callback', null); + $this->curl_method = Raven_Util::get($options, 'curl_method', 'sync'); + $this->curl_path = Raven_Util::get($options, 'curl_path', 'curl'); + $this->curl_ipv4 = Raven_util::get($options, 'curl_ipv4', true); + $this->ca_cert = Raven_util::get($options, 'ca_cert', $this->get_default_ca_cert()); + $this->verify_ssl = Raven_util::get($options, 'verify_ssl', true); + $this->curl_ssl_version = Raven_Util::get($options, 'curl_ssl_version'); + + $this->processors = $this->setProcessorsFromOptions($options); + + $this->_lasterror = null; + $this->_user = null; + $this->context = new Raven_Context(); + + if ($this->curl_method == 'async') { + $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); + } + } + + public static function getDefaultProcessors() + { + return array( + 'Raven_SanitizeDataProcessor', + ); + } + + /** + * Sets the Raven_Processor sub-classes to be used when data is processed before being + * sent to Sentry. + * + * @param $options + * @return array + */ + public function setProcessorsFromOptions($options) + { + $processors = array(); + foreach (Raven_util::get($options, 'processors', self::getDefaultProcessors()) as $processor) { + $new_processor = new $processor($this); + + if (isset($options['processorOptions']) && is_array($options['processorOptions'])) { + if (isset($options['processorOptions'][$processor]) && method_exists($processor, 'setProcessorOptions')) { + $new_processor->setProcessorOptions($options['processorOptions'][$processor]); + } + } + $processors[] = $new_processor; + } + return $processors; + } + + /** + * Parses a Raven-compatible DSN and returns an array of its values. + * + * @param string $dsn Raven compatible DSN: http://raven.readthedocs.org/en/latest/config/#the-sentry-dsn + * @return array parsed DSN + */ + public static function parseDSN($dsn) + { + $url = parse_url($dsn); + $scheme = (isset($url['scheme']) ? $url['scheme'] : ''); + if (!in_array($scheme, array('http', 'https'))) { + throw new InvalidArgumentException('Unsupported Sentry DSN scheme: ' . (!empty($scheme) ? $scheme : '')); + } + $netloc = (isset($url['host']) ? $url['host'] : null); + $netloc.= (isset($url['port']) ? ':'.$url['port'] : null); + $rawpath = (isset($url['path']) ? $url['path'] : null); + if ($rawpath) { + $pos = strrpos($rawpath, '/', 1); + if ($pos !== false) { + $path = substr($rawpath, 0, $pos); + $project = substr($rawpath, $pos + 1); + } else { + $path = ''; + $project = substr($rawpath, 1); + } + } else { + $project = null; + $path = ''; + } + $username = (isset($url['user']) ? $url['user'] : null); + $password = (isset($url['pass']) ? $url['pass'] : null); + if (empty($netloc) || empty($project) || empty($username) || empty($password)) { + throw new InvalidArgumentException('Invalid Sentry DSN: ' . $dsn); + } + + return array( + 'servers' => array(sprintf('%s://%s%s/api/%s/store/', $scheme, $netloc, $path, $project)), + 'project' => $project, + 'public_key' => $username, + 'secret_key' => $password, + ); + } + + public function getLastError() + { + return $this->_lasterror; + } + + /** + * Given an identifier, returns a Sentry searchable string. + */ + public function getIdent($ident) + { + // XXX: We don't calculate checksums yet, so we only have the ident. + return $ident; + } + + /** + * Deprecated + */ + public function message($message, $params=array(), $level=self::INFO, + $stack=false, $vars = null) + { + return $this->captureMessage($message, $params, $level, $stack, $vars); + } + + /** + * Deprecated + */ + public function exception($exception) + { + return $this->captureException($exception); + } + + /** + * Log a message to sentry + */ + public function captureMessage($message, $params=array(), $level_or_options=array(), + $stack=false, $vars = null) + { + // Gracefully handle messages which contain formatting characters, but were not + // intended to be used with formatting. + if (!empty($params)) { + $formatted_message = vsprintf($message, $params); + } else { + $formatted_message = $message; + } + + if ($level_or_options === null) { + $data = array(); + } elseif (!is_array($level_or_options)) { + $data = array( + 'level' => $level_or_options, + ); + } else { + $data = $level_or_options; + } + + $data['message'] = $formatted_message; + $data['sentry.interfaces.Message'] = array( + 'message' => $message, + 'params' => $params, + ); + + return $this->capture($data, $stack, $vars); + } + + /** + * Log an exception to sentry + */ + public function captureException($exception, $culprit_or_options=null, $logger=null, $vars=null) + { + $has_chained_exceptions = version_compare(PHP_VERSION, '5.3.0', '>='); + + if (in_array(get_class($exception), $this->exclude)) { + return null; + } + + if (!is_array($culprit_or_options)) { + $data = array(); + if ($culprit_or_options !== null) { + $data['culprit'] = $culprit_or_options; + } + } else { + $data = $culprit_or_options; + } + + // TODO(dcramer): DRY this up + $message = $exception->getMessage(); + if (empty($message)) { + $message = get_class($exception); + } + + $exc = $exception; + do { + $exc_data = array( + 'value' => $exc->getMessage(), + 'type' => get_class($exc), + 'module' => $exc->getFile() .':'. $exc->getLine(), + ); + + /**'sentry.interfaces.Exception' + * Exception::getTrace doesn't store the point at where the exception + * was thrown, so we have to stuff it in ourselves. Ugh. + */ + $trace = $exc->getTrace(); + $frame_where_exception_thrown = array( + 'file' => $exc->getFile(), + 'line' => $exc->getLine(), + ); + + array_unshift($trace, $frame_where_exception_thrown); + + // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) + if (!class_exists('Raven_Stacktrace')) { + spl_autoload_call('Raven_Stacktrace'); + } + + $exc_data['stacktrace'] = array( + 'frames' => Raven_Stacktrace::get_stack_info( + $trace, $this->trace, $this->shift_vars, $vars, $this->message_limit + ), + ); + + $exceptions[] = $exc_data; + } while ($has_chained_exceptions && $exc = $exc->getPrevious()); + + $data['message'] = $message; + $data['sentry.interfaces.Exception'] = array( + 'values' => array_reverse($exceptions), + ); + if ($logger !== null) { + $data['logger'] = $logger; + } + + if (empty($data['level'])) { + if (method_exists($exception, 'getSeverity')) { + $data['level'] = $this->translateSeverity($exception->getSeverity()); + } else { + $data['level'] = self::ERROR; + } + } + + return $this->capture($data, $trace, $vars); + } + + /** + * Log an query to sentry + */ + public function captureQuery($query, $level=self::INFO, $engine = '') + { + $data = array( + 'message' => $query, + 'level' => $level, + 'sentry.interfaces.Query' => array( + 'query' => $query + ) + ); + + if ($engine !== '') { + $data['sentry.interfaces.Query']['engine'] = $engine; + } + return $this->capture($data, false); + } + + protected function is_http_request() + { + return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; + } + + protected function get_http_data() + { + $env = $headers = array(); + + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + if (in_array($key, array('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'))) { + continue; + } + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value; + } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value; + } else { + $env[$key] = $value; + } + } + + $result = array( + 'method' => $this->_server_variable('REQUEST_METHOD'), + 'url' => $this->get_current_url(), + 'query_string' => $this->_server_variable('QUERY_STRING'), + ); + + // dont set this as an empty array as PHP will treat it as a numeric array + // instead of a mapping which goes against the defined Sentry spec + if (!empty($_POST)) { + $result['data'] = $_POST; + } + if (!empty($_COOKIE)) { + $result['cookies'] = $_COOKIE; + } + if (!empty($headers)) { + $result['headers'] = $headers; + } + if (!empty($env)) { + $result['env'] = $env; + } + + return array( + 'sentry.interfaces.Http' => $result, + ); + } + + protected function get_user_data() + { + $user = $this->context->user; + if ($user === null) { + if (!session_id()) { + return array(); + } + $user = array( + 'id' => session_id(), + ); + if (!empty($_SESSION)) { + $user['data'] = $_SESSION; + } + } + return array( + 'sentry.interfaces.User' => $user, + ); + } + + protected function get_extra_data() + { + return $this->extra_data; + } + + public function get_default_data() + { + return array( + 'server_name' => $this->name, + 'project' => $this->project, + 'site' => $this->site, + 'logger' => $this->logger, + 'tags' => $this->tags, + 'platform' => 'php', + ); + } + + public function capture($data, $stack, $vars = null) + { + if (!isset($data['timestamp'])) { + $data['timestamp'] = gmdate('Y-m-d\TH:i:s\Z'); + } + if (!isset($data['level'])) { + $data['level'] = self::ERROR; + } + if (!isset($data['tags'])) { + $data['tags'] = array(); + } + if (!isset($data['extra'])) { + $data['extra'] = array(); + } + if (!isset($data['event_id'])) { + $data['event_id'] = $this->uuid4(); + } + + if (isset($data['message'])) { + $data['message'] = substr($data['message'], 0, $this->message_limit); + } + + $data = array_merge($this->get_default_data(), $data); + + if ($this->is_http_request()) { + $data = array_merge($this->get_http_data(), $data); + } + + $data = array_merge($this->get_user_data(), $data); + + if ($this->release) { + $data['release'] = $this->release; + } + + $data['tags'] = array_merge( + $this->tags, + $this->context->tags, + $data['tags']); + + $data['extra'] = array_merge( + $this->get_extra_data(), + $this->context->extra, + $data['extra']); + + if ((!$stack && $this->auto_log_stacks) || $stack === true) { + $stack = debug_backtrace(); + + // Drop last stack + array_shift($stack); + } + + if (!empty($stack)) { + // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) + if (!class_exists('Raven_Stacktrace')) { + spl_autoload_call('Raven_Stacktrace'); + } + + if (!isset($data['sentry.interfaces.Stacktrace'])) { + $data['sentry.interfaces.Stacktrace'] = array( + 'frames' => Raven_Stacktrace::get_stack_info( + $stack, $this->trace, $this->shift_vars, $vars, $this->message_limit + ), + ); + } + } + + $this->sanitize($data); + $this->process($data); + + if (!$this->store_errors_for_bulk_send) { + $this->send($data); + } else { + if (empty($this->error_data)) { + $this->error_data = array(); + } + $this->error_data[] = $data; + } + + return $data['event_id']; + } + + public function sanitize(&$data) + { + // manually trigger autoloading, as it's not done in some edge cases due to PHP bugs (see #60149) + if (!class_exists('Raven_Serializer')) { + spl_autoload_call('Raven_Serializer'); + } + + $data = Raven_Serializer::serialize($data); + } + + /** + * Process data through all defined Raven_Processor sub-classes + * + * @param array $data Associative array of data to log + */ + public function process(&$data) + { + foreach ($this->processors as $processor) { + $processor->process($data); + } + } + + public function sendUnsentErrors() + { + if (!empty($this->error_data)) { + foreach ($this->error_data as $data) { + $this->send($data); + } + unset($this->error_data); + } + if ($this->store_errors_for_bulk_send) { + //in case an error occurs after this is called, on shutdown, send any new errors. + $this->store_errors_for_bulk_send = !defined('RAVEN_CLIENT_END_REACHED'); + } + } + + /** + * Wrapper to handle encoding and sending data to all defined Sentry servers + * + * @param array $data Associative array of data to log + */ + public function send($data) + { + if (is_callable($this->send_callback) && !call_user_func($this->send_callback, $data)) { + // if send_callback returns falsely, end native send + return; + } + + if (!$this->servers) { + return; + } + + $message = Raven_Compat::json_encode($data); + + if (function_exists("gzcompress")) { + $message = gzcompress($message); + } + $message = base64_encode($message); // PHP's builtin curl_* function are happy without this, but the exec method requires it + + foreach ($this->servers as $url) { + $client_string = 'raven-php/' . self::VERSION; + $timestamp = microtime(true); + $headers = array( + 'User-Agent' => $client_string, + 'X-Sentry-Auth' => $this->get_auth_header( + $timestamp, $client_string, $this->public_key, + $this->secret_key), + 'Content-Type' => 'application/octet-stream' + ); + + $this->send_remote($url, $message, $headers); + } + } + + /** + * Send data to Sentry + * + * @param string $url Full URL to Sentry + * @param array $data Associative array of data to log + * @param array $headers Associative array of headers + */ + private function send_remote($url, $data, $headers=array()) + { + $parts = parse_url($url); + $parts['netloc'] = $parts['host'].(isset($parts['port']) ? ':'.$parts['port'] : null); + $this->send_http($url, $data, $headers); + } + + protected function get_default_ca_cert() + { + return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem'; + } + + protected function get_curl_options() + { + $options = array( + CURLOPT_VERBOSE => false, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_SSL_VERIFYPEER => $this->verify_ssl, + CURLOPT_CAINFO => $this->ca_cert, + CURLOPT_USERAGENT => 'raven-php/' . self::VERSION, + ); + if ($this->http_proxy) { + $options[CURLOPT_PROXY] = $this->http_proxy; + } + if ($this->curl_ssl_version) { + $options[CURLOPT_SSLVERSION] = $this->curl_ssl_version; + } + if ($this->curl_ipv4) { + $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4; + } + if (defined('CURLOPT_TIMEOUT_MS')) { + // MS is available in curl >= 7.16.2 + $timeout = max(1, ceil(1000 * $this->timeout)); + + // some versions of PHP 5.3 don't have this defined correctly + if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { + //see http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 + define('CURLOPT_CONNECTTIMEOUT_MS', 156); + } + + $options[CURLOPT_CONNECTTIMEOUT_MS] = $timeout; + $options[CURLOPT_TIMEOUT_MS] = $timeout; + } else { + // fall back to the lower-precision timeout. + $timeout = max(1, ceil($this->timeout)); + $options[CURLOPT_CONNECTTIMEOUT] = $timeout; + $options[CURLOPT_TIMEOUT] = $timeout; + } + return $options; + } + + /** + * Send the message over http to the sentry url given + * + * @param string $url URL of the Sentry instance to log to + * @param array $data Associative array of data to log + * @param array $headers Associative array of headers + */ + private function send_http($url, $data, $headers=array()) + { + if ($this->curl_method == 'async') { + $this->_curl_handler->enqueue($url, $data, $headers); + } elseif ($this->curl_method == 'exec') { + $this->send_http_asynchronous_curl_exec($url, $data, $headers); + } else { + $this->send_http_synchronous($url, $data, $headers); + } + } + + /** + * Send the cURL to Sentry asynchronously. No errors will be returned from cURL + * + * @param string $url URL of the Sentry instance to log to + * @param array $data Associative array of data to log + * @param array $headers Associative array of headers + * @return bool + */ + private function send_http_asynchronous_curl_exec($url, $data, $headers) + { + // TODO(dcramer): support ca_cert + $cmd = $this->curl_path.' -X POST '; + foreach ($headers as $key => $value) { + $cmd .= '-H \''. $key. ': '. $value. '\' '; + } + $cmd .= '-d \''. $data .'\' '; + $cmd .= '\''. $url .'\' '; + $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) + $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background + + exec($cmd); + + return true; // The exec method is just fire and forget, so just assume it always works + } + + /** + * Send a blocking cURL to Sentry and check for errors from cURL + * + * @param string $url URL of the Sentry instance to log to + * @param array $data Associative array of data to log + * @param array $headers Associative array of headers + * @return bool + */ + private function send_http_synchronous($url, $data, $headers) + { + $new_headers = array(); + foreach ($headers as $key => $value) { + array_push($new_headers, $key .': '. $value); + } + // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216) + $new_headers[] = 'Expect:'; + + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_HTTPHEADER, $new_headers); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + + $options = $this->get_curl_options(); + $ca_cert = $options[CURLOPT_CAINFO]; + unset($options[CURLOPT_CAINFO]); + curl_setopt_array($curl, $options); + + curl_exec($curl); + + $errno = curl_errno($curl); + // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE + if ($errno == 60 || $errno == 77) { + curl_setopt($curl, CURLOPT_CAINFO, $ca_cert); + curl_exec($curl); + } + + $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $success = ($code == 200); + if (!$success) { + // It'd be nice just to raise an exception here, but it's not very PHP-like + $this->_lasterror = curl_error($curl); + } else { + $this->_lasterror = null; + } + curl_close($curl); + + return $success; + } + + /** + * Generate a Sentry authorization header string + * + * @param string $timestamp Timestamp when the event occurred + * @param string $client HTTP client name (not Raven_Client object) + * @param string $api_key Sentry API key + * @param string $secret_key Sentry API key + * @return string + */ + protected function get_auth_header($timestamp, $client, $api_key, $secret_key) + { + $header = array( + sprintf('sentry_timestamp=%F', $timestamp), + "sentry_client={$client}", + sprintf('sentry_version=%s', self::PROTOCOL), + ); + + if ($api_key) { + $header[] = "sentry_key={$api_key}"; + } + + if ($secret_key) { + $header[] = "sentry_secret={$secret_key}"; + } + + + return sprintf('Sentry %s', implode(', ', $header)); + } + + /** + * Generate an uuid4 value + * + * @return string + */ + private function uuid4() + { + $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + // 32 bits for "time_low" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + + // 16 bits for "time_mid" + mt_rand(0, 0xffff), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, + + // 48 bits for "node" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + + return str_replace('-', '', $uuid); + } + + /** + * Return the URL for the current request + * + * @return string|null + */ + private function get_current_url() + { + // When running from commandline the REQUEST_URI is missing. + if (!isset($_SERVER['REQUEST_URI'])) { + return null; + } + + $schema = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' + || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; + + // HTTP_HOST is a client-supplied header that is optional in HTTP 1.0 + $host = (!empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] + : (!empty($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] + : (!empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''))); + + return $schema . $host . $_SERVER['REQUEST_URI']; + } + + /** + * Get the value of a key from $_SERVER + * + * @param string $key Key whose value you wish to obtain + * @return string Key's value + */ + private function _server_variable($key) + { + if (isset($_SERVER[$key])) { + return $_SERVER[$key]; + } + + return ''; + } + + /** + * Translate a PHP Error constant into a Sentry log level group + * + * @param string $severity PHP E_$x error constant + * @return string Sentry log level group + */ + public function translateSeverity($severity) + { + if (is_array($this->severity_map) && isset($this->severity_map[$severity])) { + return $this->severity_map[$severity]; + } + switch ($severity) { + case E_ERROR: return Raven_Client::ERROR; + case E_WARNING: return Raven_Client::WARN; + case E_PARSE: return Raven_Client::ERROR; + case E_NOTICE: return Raven_Client::INFO; + case E_CORE_ERROR: return Raven_Client::ERROR; + case E_CORE_WARNING: return Raven_Client::WARN; + case E_COMPILE_ERROR: return Raven_Client::ERROR; + case E_COMPILE_WARNING: return Raven_Client::WARN; + case E_USER_ERROR: return Raven_Client::ERROR; + case E_USER_WARNING: return Raven_Client::WARN; + case E_USER_NOTICE: return Raven_Client::INFO; + case E_STRICT: return Raven_Client::INFO; + case E_RECOVERABLE_ERROR: return Raven_Client::ERROR; + } + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + switch ($severity) { + case E_DEPRECATED: return Raven_Client::WARN; + case E_USER_DEPRECATED: return Raven_Client::WARN; + } + } + return Raven_Client::ERROR; + } + + /** + * Provide a map of PHP Error constants to Sentry logging groups to use instead + * of the defaults in translateSeverity() + * + * @param array $map + */ + public function registerSeverityMap($map) + { + $this->severity_map = $map; + } + + /** + * Convenience function for setting a user's ID and Email + * + * @param string $id User's ID + * @param string|null $email User's email + * @param array $data Additional user data + */ + public function set_user_data($id, $email=null, $data=array()) + { + $this->user_context(array_merge(array( + 'id' => $id, + 'email' => $email, + ), $data)); + } + + /** + * Sets user context. + * + * @param array $data Associative array of user data + */ + public function user_context($data) + { + $this->context->user = $data; + } + + /** + * Appends tags context. + * + * @param array $data Associative array of tags + */ + public function tags_context($data) + { + $this->context->tags = array_merge($this->context->tags, $data); + } + + /** + * Appends additional context. + * + * @param array $data Associative array of extra data + */ + public function extra_context($data) + { + $this->context->extra = array_merge($this->context->extra, $data); + } + + /** + * @param array $processors + */ + public function setProcessors(array $processors) + { + $this->processors = $processors; + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/Compat.php b/htdocs/includes/raven/raven/lib/Raven/Compat.php new file mode 100644 index 00000000000..08c85a99740 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/Compat.php @@ -0,0 +1,136 @@ + $size) { + $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00)); + } else { + $key = str_pad($key, $size, chr(0x00)); + } + + $keyLastPos = strlen($key) - 1; + for ($i = 0; $i < $keyLastPos; $i++) { + $opad[$i] = $opad[$i] ^ $key[$i]; + $ipad[$i] = $ipad[$i] ^ $key[$i]; + } + + $output = $algo($opad.pack($pack, $algo($ipad.$data))); + + return ($raw_output) ? pack($pack, $output) : $output; + } + + /** + * Note that we discard the options given to be compatible + * with PHP < 5.3 + */ + public static function json_encode($value, $options=0) + { + if (function_exists('json_encode')) { + return json_encode($value); + } + + return self::_json_encode($value); + } + + /** + * Implementation taken from + * http://www.mike-griffiths.co.uk/php-json_encode-alternative/ + */ + public static function _json_encode($value) + { + static $jsonReplaces = array( + array('\\', '/', "\n", "\t", "\r", "\b", "\f", '"'), + array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"')); + + if (is_null($value)) { + return 'null'; + } + if ($value === false) { + return 'false'; + } + if ($value === true) { + return 'true'; + } + + if (is_scalar($value)) { + + // Always use '.' for floats. + if (is_float($value)) { + return floatval(str_replace(',', '.', strval($value))); + } + if (is_string($value)) { + return sprintf('"%s"', + str_replace($jsonReplaces[0], $jsonReplaces[1], $value)); + } else { + return $value; + } + } + + $isList = true; + for ($i = 0, reset($value); $i $v) { + $result[] = self::_json_encode($k) . ':' . self::_json_encode($v); + } + + return '{' . join(',', $result) . '}'; + } + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/Context.php b/htdocs/includes/raven/raven/lib/Raven/Context.php new file mode 100644 index 00000000000..ef56adb4ab5 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/Context.php @@ -0,0 +1,23 @@ +clear(); + } + + /** + * Clean up existing context. + */ + public function clear() + { + $this->tags = array(); + $this->extra = array(); + $this->user = null; + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/CurlHandler.php b/htdocs/includes/raven/raven/lib/Raven/CurlHandler.php new file mode 100644 index 00000000000..cce4f8a9851 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/CurlHandler.php @@ -0,0 +1,117 @@ +options = $options; + $this->multi_handle = curl_multi_init(); + $this->requests = array(); + $this->join_timeout = 5; + + register_shutdown_function(array($this, 'join')); + } + + public function __destruct() + { + $this->join(); + } + + public function enqueue($url, $data=null, $headers=array()) + { + $ch = curl_init(); + + $new_headers = array(); + foreach ($headers as $key => $value) { + array_push($new_headers, $key .': '. $value); + } + // XXX(dcramer): Prevent 100-continue response form server (Fixes GH-216) + $new_headers[] = 'Expect:'; + + curl_setopt($ch, CURLOPT_HTTPHEADER, $new_headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_URL, $url); + + curl_setopt_array($ch, $this->options); + + if (isset($data)) { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } + + curl_multi_add_handle($this->multi_handle, $ch); + + $fd = (int)$ch; + $this->requests[$fd] = 1; + + $this->select(); + + return $fd; + } + + public function join($timeout=null) + { + if (!isset($timeout)) { + $timeout = $this->join_timeout; + } + $start = time(); + do { + $this->select(); + if (count($this->requests) === 0) { + break; + } + usleep(10000); + } while ($timeout !== 0 && time() - $start > $timeout); + } + + // http://se2.php.net/manual/en/function.curl-multi-exec.php + private function select() + { + do { + $mrc = curl_multi_exec($this->multi_handle, $active); + } while ($mrc == CURLM_CALL_MULTI_PERFORM); + + while ($active && $mrc == CURLM_OK) { + if (curl_multi_select($this->multi_handle) !== -1) { + do { + $mrc = curl_multi_exec($this->multi_handle, $active); + } while ($mrc == CURLM_CALL_MULTI_PERFORM); + } else { + return; + } + } + + while ($info = curl_multi_info_read($this->multi_handle)) { + $ch = $info['handle']; + $fd = (int)$ch; + + curl_multi_remove_handle($this->multi_handle, $ch); + + if (!isset($this->requests[$fd])) { + return; + } + + unset($this->requests[$fd]); + } + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/ErrorHandler.php b/htdocs/includes/raven/raven/lib/Raven/ErrorHandler.php new file mode 100644 index 00000000000..3632e62e269 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/ErrorHandler.php @@ -0,0 +1,175 @@ +registerExceptionHandler(); + * $error_handler->registerErrorHandler(); + * $error_handler->registerShutdownFunction(); + * + * @package raven + */ + +class Raven_ErrorHandler +{ + private $old_exception_handler; + private $call_existing_exception_handler = false; + private $old_error_handler; + private $call_existing_error_handler = false; + private $reservedMemory; + private $send_errors_last = false; + private $error_types = -1; + + /** + * @var array + * Error types that can be processed by the handler + */ + private $validErrorTypes = array( + E_ERROR, + E_WARNING, + E_PARSE, + E_NOTICE, + E_CORE_ERROR, + E_CORE_WARNING, + E_COMPILE_ERROR, + E_COMPILE_WARNING, + E_USER_ERROR, + E_USER_WARNING, + E_USER_NOTICE, + E_STRICT, + E_RECOVERABLE_ERROR, + E_DEPRECATED, + E_USER_DEPRECATED, + ); + + /** + * @var array + * Error types that are always processed by the handler + */ + private $defaultErrorTypes = array( + E_ERROR, + E_PARSE, + E_CORE_ERROR, + E_CORE_WARNING, + E_COMPILE_ERROR, + E_COMPILE_WARNING, + E_STRICT, + ); + + public function __construct($client, $send_errors_last = false) + { + $this->client = $client; + register_shutdown_function(array($this, 'detectShutdown')); + if ($send_errors_last) { + $this->send_errors_last = true; + $this->client->store_errors_for_bulk_send = true; + register_shutdown_function(array($this->client, 'sendUnsentErrors')); + } + } + + public function handleException($e, $isError = false, $vars = null) + { + $e->event_id = $this->client->getIdent($this->client->captureException($e, null, null, $vars)); + + if (!$isError && $this->call_existing_exception_handler && $this->old_exception_handler) { + call_user_func($this->old_exception_handler, $e); + } + } + + public function handleError($code, $message, $file = '', $line = 0, $context=array()) + { + if ($this->error_types & $code & error_reporting()) { + $e = new ErrorException($message, 0, $code, $file, $line); + $this->handleException($e, true, $context); + } + + if ($this->call_existing_error_handler) { + if ($this->old_error_handler) { + return call_user_func($this->old_error_handler, $code, $message, $file, $line, $context); + } else { + return false; + } + } + } + + /** + * Nothing by default, use it in child classes for catching other types of errors + * Only constants from $this->validErrorTypes can be used + * + * @return array + */ + protected function getAdditionalErrorTypesToProcess() + { + return array(); + } + + /** + * @return array + */ + private function getErrorTypesToProcess() + { + $additionalErrorTypes = array_intersect($this->getAdditionalErrorTypesToProcess(), $this->validErrorTypes); + // array_unique so bitwise "or" operation wouldn't fail if some error type gets repeated + return array_unique($this->defaultErrorTypes + $additionalErrorTypes); + } + + public function handleFatalError() + { + if (null === $lastError = error_get_last()) { + return; + } + + unset($this->reservedMemory); + + $errors = 0; + foreach ($this->getErrorTypesToProcess() as $errorType) { + $errors |= $errorType; + } + + if ($lastError['type'] & $errors) { + $e = new ErrorException( + @$lastError['message'], @$lastError['type'], @$lastError['type'], + @$lastError['file'], @$lastError['line'] + ); + $this->handleException($e, true); + } + } + + public function registerExceptionHandler($call_existing_exception_handler = true) + { + $this->old_exception_handler = set_exception_handler(array($this, 'handleException')); + $this->call_existing_exception_handler = $call_existing_exception_handler; + } + + public function registerErrorHandler($call_existing_error_handler = true, $error_types = -1) + { + $this->error_types = $error_types; + $this->old_error_handler = set_error_handler(array($this, 'handleError'), error_reporting()); + $this->call_existing_error_handler = $call_existing_error_handler; + } + + public function registerShutdownFunction($reservedMemorySize = 10) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); + } + + public function detectShutdown() + { + if (!defined('RAVEN_CLIENT_END_REACHED')) { + define('RAVEN_CLIENT_END_REACHED', true); + } + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/Processor.php b/htdocs/includes/raven/raven/lib/Raven/Processor.php new file mode 100644 index 00000000000..7e37400d14e --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/Processor.php @@ -0,0 +1,20 @@ +client = $client; + } + + /** + * Process and sanitize data, modifying the existing value if necessary. + * + * @param array $data Array of log data + */ + abstract public function process(&$data); +} diff --git a/htdocs/includes/raven/raven/lib/Raven/SanitizeDataProcessor.php b/htdocs/includes/raven/raven/lib/Raven/SanitizeDataProcessor.php new file mode 100644 index 00000000000..dbf1c78bb29 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/SanitizeDataProcessor.php @@ -0,0 +1,102 @@ +client = $client; + $this->fields_re = self::FIELDS_RE; + $this->values_re = self::VALUES_RE; + } + + /** + * Override the default processor options + * + * @param array $options Associative array of processor options + */ + public function setProcessorOptions(array $options) + { + if (isset($options['fields_re'])) { + $this->fields_re = $options['fields_re']; + } + + if (isset($options['values_re'])) { + $this->values_re = $options['values_re']; + } + } + + /** + * Replace any array values with our mask if the field name or the value matches a respective regex + * + * @param mixed $item Associative array value + * @param string $key Associative array key + */ + public function sanitize(&$item, $key) + { + if (empty($item)) { + return; + } + + if (preg_match($this->values_re, $item)) { + $item = self::MASK; + } + + if (empty($key)) { + return; + } + + if (preg_match($this->fields_re, $key)) { + $item = self::MASK; + } + } + + public function process(&$data) + { + array_walk_recursive($data, array($this, 'sanitize')); + } + + /** + * @return string + */ + public function getFieldsRe() + { + return $this->fields_re; + } + + /** + * @param string $fields_re + */ + public function setFieldsRe($fields_re) + { + $this->fields_re = $fields_re; + } + + /** + * @return string + */ + public function getValuesRe() + { + return $this->values_re; + } + + /** + * @param string $values_re + */ + public function setValuesRe($values_re) + { + $this->values_re = $values_re; + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/Serializer.php b/htdocs/includes/raven/raven/lib/Raven/Serializer.php new file mode 100644 index 00000000000..3423dbf0fe9 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/Serializer.php @@ -0,0 +1,77 @@ + $v) { + $new[self::serializeValue($k)] = self::serialize($v, $max_depth, $_depth + 1); + } + + return $new; + } else { + return self::serializeValue($value); + } + } + + public static function serializeValue($value) + { + if ($value === null) { + return 'null'; + } elseif ($value === false) { + return 'false'; + } elseif ($value === true) { + return 'true'; + } elseif (is_float($value) && (int) $value == $value) { + return $value.'.0'; + } elseif (is_object($value) || gettype($value) == 'object') { + return 'Object '.get_class($value); + } elseif (is_resource($value)) { + return 'Resource '.get_resource_type($value); + } elseif (is_array($value)) { + return 'Array of length ' . count($value); + } elseif (is_integer($value)) { + return (integer) $value; + } else { + $value = (string) $value; + + if (function_exists('mb_convert_encoding')) { + $value = mb_convert_encoding($value, 'UTF-8', 'UTF-8'); + } + + return $value; + } + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/Stacktrace.php b/htdocs/includes/raven/raven/lib/Raven/Stacktrace.php new file mode 100644 index 00000000000..bc5d4f8195b --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/Stacktrace.php @@ -0,0 +1,252 @@ + $abs_path, + 'filename' => $context['filename'], + 'lineno' => (int) $context['lineno'], + 'module' => $module, + 'function' => $nextframe['function'], + 'pre_context' => $context['prefix'], + 'context_line' => $context['line'], + 'post_context' => $context['suffix'], + ); + // dont set this as an empty array as PHP will treat it as a numeric array + // instead of a mapping which goes against the defined Sentry spec + if (!empty($vars)) { + foreach ($vars as $key => $value) { + if (is_string($value) || is_numeric($value)) { + $vars[$key] = substr($value, 0, $frame_var_limit); + } + } + $frame['vars'] = $vars; + } + + $result[] = $frame; + } + + return array_reverse($result); + } + + public static function get_caller_frame_context($frame) + { + if (!isset($frame['args'])) { + return array(); + } + + $i = 1; + $args = array(); + foreach ($frame['args'] as $arg) { + $args['param'.$i] = $arg; + $i++; + } + return $args; + } + + public static function get_frame_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) + { + // The reflection API seems more appropriate if we associate it with the frame + // where the function is actually called (since we're treating them as function context) + if (!isset($frame['function'])) { + return array(); + } + + if (!isset($frame['args'])) { + return array(); + } + + if (strpos($frame['function'], '__lambda_func') !== false) { + return array(); + } + if (isset($frame['class']) && $frame['class'] == 'Closure') { + return array(); + } + if (strpos($frame['function'], '{closure}') !== false) { + return array(); + } + if (in_array($frame['function'], self::$statements)) { + if (empty($frame['args'])) { + // No arguments + return array(); + } else { + // Sanitize the file path + return array($frame['args'][0]); + } + } + try { + if (isset($frame['class'])) { + if (method_exists($frame['class'], $frame['function'])) { + $reflection = new ReflectionMethod($frame['class'], $frame['function']); + } elseif ($frame['type'] === '::') { + $reflection = new ReflectionMethod($frame['class'], '__callStatic'); + } else { + $reflection = new ReflectionMethod($frame['class'], '__call'); + } + } else { + $reflection = new ReflectionFunction($frame['function']); + } + } catch (ReflectionException $e) { + return array(); + } + + $params = $reflection->getParameters(); + + $args = array(); + foreach ($frame['args'] as $i => $arg) { + if (isset($params[$i])) { + // Assign the argument by the parameter name + if (is_array($arg)) { + foreach ($arg as $key => $value) { + if (is_string($value) || is_numeric($value)) { + $arg[$key] = substr($value, 0, $frame_arg_limit); + } + } + } + $args[$params[$i]->name] = $arg; + } else { + // TODO: Sentry thinks of these as context locals, so they must be named + // Assign the argument by number + // $args[$i] = $arg; + } + } + + return $args; + } + + private static function read_source_file($filename, $lineno, $context_lines = 5) + { + $frame = array( + 'prefix' => array(), + 'line' => '', + 'suffix' => array(), + 'filename' => $filename, + 'lineno' => $lineno, + ); + + if ($filename === null || $lineno === null) { + return $frame; + } + + // Code which is eval'ed have a modified filename.. Extract the + // correct filename + linenumber from the string. + $matches = array(); + $matched = preg_match("/^(.*?)\((\d+)\) : eval\(\)'d code$/", + $filename, $matches); + if ($matched) { + $frame['filename'] = $filename = $matches[1]; + $frame['lineno'] = $lineno = $matches[2]; + } + + // In the case of an anonymous function, the filename is sent as: + // "() : runtime-created function" + // Extract the correct filename + linenumber from the string. + $matches = array(); + $matched = preg_match("/^(.*?)\((\d+)\) : runtime-created function$/", + $filename, $matches); + if ($matched) { + $frame['filename'] = $filename = $matches[1]; + $frame['lineno'] = $lineno = $matches[2]; + } + + try { + $file = new SplFileObject($filename); + $target = max(0, ($lineno - ($context_lines + 1))); + $file->seek($target); + $cur_lineno = $target+1; + while (!$file->eof()) { + $line = rtrim($file->current(), "\r\n"); + if ($cur_lineno == $lineno) { + $frame['line'] = $line; + } elseif ($cur_lineno < $lineno) { + $frame['prefix'][] = $line; + } elseif ($cur_lineno > $lineno) { + $frame['suffix'][] = $line; + } + $cur_lineno++; + if ($cur_lineno > $lineno + $context_lines) { + break; + } + $file->next(); + } + } catch (RuntimeException $exc) { + return $frame; + } + + return $frame; + } +} diff --git a/htdocs/includes/raven/raven/lib/Raven/Util.php b/htdocs/includes/raven/raven/lib/Raven/Util.php new file mode 100644 index 00000000000..dbd2d11d0a5 --- /dev/null +++ b/htdocs/includes/raven/raven/lib/Raven/Util.php @@ -0,0 +1,33 @@ + + + + + + ./test/Raven/ + + + + + + ./lib/Raven/ + + + diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/ClientTest.php b/htdocs/includes/raven/raven/test/Raven/Tests/ClientTest.php new file mode 100644 index 00000000000..cdec2fd4756 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/ClientTest.php @@ -0,0 +1,624 @@ +__sent_events; + } + public function send($data) + { + if (is_callable($this->send_callback) && !call_user_func($this->send_callback, $data)) { + // if send_callback returns falsely, end native send + return; + } + $this->__sent_events[] = $data; + } + public function is_http_request() + { + return true; + } + public function get_auth_header($timestamp, $client, $api_key, $secret_key) + { + return parent::get_auth_header($timestamp, $client, $api_key, $secret_key); + } + public function get_http_data() + { + return parent::get_http_data(); + } + public function get_user_data() + { + return parent::get_user_data(); + } +} + +class Raven_Tests_ClientTest extends PHPUnit_Framework_TestCase +{ + private function create_exception() + { + try { + throw new Exception('Foo bar'); + } catch (Exception $ex) { + return $ex; + } + } + + private function create_chained_exception() + { + try { + throw new Exception('Foo bar'); + } catch (Exception $ex) { + try { + throw new Exception('Child exc', 0, $ex); + } catch (Exception $ex2) { + return $ex2; + } + } + } + + public function testParseDsnHttp() + { + $result = Raven_Client::parseDsn('http://public:secret@example.com/1'); + + $this->assertEquals($result['project'], 1); + $this->assertEquals($result['servers'], array('http://example.com/api/1/store/')); + $this->assertEquals($result['public_key'], 'public'); + $this->assertEquals($result['secret_key'], 'secret'); + } + + public function testParseDsnHttps() + { + $result = Raven_Client::parseDsn('https://public:secret@example.com/1'); + + $this->assertEquals($result['project'], 1); + $this->assertEquals($result['servers'], array('https://example.com/api/1/store/')); + $this->assertEquals($result['public_key'], 'public'); + $this->assertEquals($result['secret_key'], 'secret'); + } + + public function testParseDsnPath() + { + $result = Raven_Client::parseDsn('http://public:secret@example.com/app/1'); + + $this->assertEquals($result['project'], 1); + $this->assertEquals($result['servers'], array('http://example.com/app/api/1/store/')); + $this->assertEquals($result['public_key'], 'public'); + $this->assertEquals($result['secret_key'], 'secret'); + } + + public function testParseDsnPort() + { + $result = Raven_Client::parseDsn('http://public:secret@example.com:9000/app/1'); + + $this->assertEquals($result['project'], 1); + $this->assertEquals($result['servers'], array('http://example.com:9000/app/api/1/store/')); + $this->assertEquals($result['public_key'], 'public'); + $this->assertEquals($result['secret_key'], 'secret'); + } + + public function testParseDsnInvalidScheme() + { + try { + Raven_Client::parseDsn('gopher://public:secret@/1'); + $this->fail(); + } catch (Exception $e) { + return; + } + } + + public function testParseDsnMissingNetloc() + { + try { + Raven_Client::parseDsn('http://public:secret@/1'); + $this->fail(); + } catch (Exception $e) { + return; + } + } + + public function testParseDsnMissingProject() + { + try { + Raven_Client::parseDsn('http://public:secret@example.com'); + $this->fail(); + } catch (Exception $e) { + return; + } + } + + /** + * @expectedException InvalidArgumentException + */ + public function testParseDsnMissingPublicKey() + { + Raven_Client::parseDsn('http://:secret@example.com/1'); + } + /** + * @expectedException InvalidArgumentException + */ + public function testParseDsnMissingSecretKey() + { + Raven_Client::parseDsn('http://public@example.com/1'); + } + + public function testDsnFirstArgument() + { + $client = new Raven_Client('http://public:secret@example.com/1'); + + $this->assertEquals($client->project, 1); + $this->assertEquals($client->servers, array('http://example.com/api/1/store/')); + $this->assertEquals($client->public_key, 'public'); + $this->assertEquals($client->secret_key, 'secret'); + } + + public function testDsnFirstArgumentWithOptions() + { + $client = new Raven_Client('http://public:secret@example.com/1', array( + 'site' => 'foo', + )); + + $this->assertEquals($client->project, 1); + $this->assertEquals($client->servers, array('http://example.com/api/1/store/')); + $this->assertEquals($client->public_key, 'public'); + $this->assertEquals($client->secret_key, 'secret'); + $this->assertEquals($client->site, 'foo'); + } + + public function testOptionsFirstArgument() + { + $client = new Raven_Client(array( + 'servers' => array('http://example.com/api/1/store/'), + 'project' => 1, + )); + + $this->assertEquals($client->servers, array('http://example.com/api/1/store/')); + } + + public function testOptionsFirstArgumentWithOptions() + { + $client = new Raven_Client(array( + 'servers' => array('http://example.com/api/1/store/'), + 'project' => 1, + ), array( + 'site' => 'foo', + )); + + $this->assertEquals($client->servers, array('http://example.com/api/1/store/')); + $this->assertEquals($client->site, 'foo'); + } + + public function testOptionsExtraData() + { + $client = new Dummy_Raven_Client(array('extra' => array('foo' => 'bar'))); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['extra']['foo'], 'bar'); + } + + public function testCaptureMessageDoesHandleUninterpolatedMessage() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s'); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['message'], 'Test Message %s'); + } + + public function testCaptureMessageDoesHandleInterpolatedMessage() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['message'], 'Test Message foo'); + } + + public function testCaptureMessageSetsInterface() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['sentry.interfaces.Message'], array( + 'message' => 'Test Message %s', + 'params' => array('foo'), + )); + } + + public function testCaptureMessageHandlesOptionsAsThirdArg() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s', array('foo'), array( + 'level' => Dummy_Raven_Client::WARNING, + 'extra' => array('foo' => 'bar') + )); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); + $this->assertEquals($event['extra']['foo'], 'bar'); + } + + public function testCaptureMessageHandlesLevelAsThirdArg() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s', array('foo'), Dummy_Raven_Client::WARNING); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); + } + + public function testCaptureExceptionSetsInterfaces() + { + # TODO: it'd be nice if we could mock the stacktrace extraction function here + $client = new Dummy_Raven_Client(); + $ex = $this->create_exception(); + $client->captureException($ex); + + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + + $exc = $event['sentry.interfaces.Exception']; + $this->assertEquals(count($exc['values']), 1); + $this->assertEquals($exc['values'][0]['value'], 'Foo bar'); + $this->assertEquals($exc['values'][0]['type'], 'Exception'); + $this->assertFalse(empty($exc['values'][0]['module'])); + + $this->assertFalse(empty($exc['values'][0]['stacktrace']['frames'])); + $frames = $exc['values'][0]['stacktrace']['frames']; + $frame = $frames[count($frames) - 1]; + $this->assertTrue($frame['lineno'] > 0); + $this->assertEquals($frame['module'], 'ClientTest.php:Raven_Tests_ClientTest'); + $this->assertEquals($frame['function'], 'create_exception'); + $this->assertFalse(isset($frame['vars'])); + $this->assertEquals($frame['context_line'], ' throw new Exception(\'Foo bar\');'); + $this->assertFalse(empty($frame['pre_context'])); + $this->assertFalse(empty($frame['post_context'])); + } + + public function testCaptureExceptionChainedException() + { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + $this->markTestSkipped('PHP 5.3 required for chained exceptions.'); + } + + # TODO: it'd be nice if we could mock the stacktrace extraction function here + $client = new Dummy_Raven_Client(); + $ex = $this->create_chained_exception(); + $client->captureException($ex); + + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + + $exc = $event['sentry.interfaces.Exception']; + $this->assertEquals(count($exc['values']), 2); + $this->assertEquals($exc['values'][0]['value'], 'Foo bar'); + $this->assertEquals($exc['values'][1]['value'], 'Child exc'); + } + + public function testCaptureExceptionDifferentLevelsInChainedExceptionsBug() + { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + $this->markTestSkipped('PHP 5.3 required for chained exceptions.'); + } + + $client = new Dummy_Raven_Client(); + $e1 = new ErrorException('First', 0, E_DEPRECATED); + $e2 = new ErrorException('Second', 0, E_NOTICE, __FILE__, __LINE__, $e1); + $e3 = new ErrorException('Third', 0, E_ERROR, __FILE__, __LINE__, $e2); + + $client->captureException($e1); + $client->captureException($e2); + $client->captureException($e3); + $events = $client->getSentEvents(); + + $event = array_pop($events); + $this->assertEquals($event['level'], Dummy_Raven_Client::ERROR); + + $event = array_pop($events); + $this->assertEquals($event['level'], Dummy_Raven_Client::INFO); + + $event = array_pop($events); + $this->assertEquals($event['level'], Dummy_Raven_Client::WARNING); + } + + public function testCaptureExceptionHandlesOptionsAsSecondArg() + { + $client = new Dummy_Raven_Client(); + $ex = $this->create_exception(); + $client->captureException($ex, array('culprit' => 'test')); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['culprit'], 'test'); + } + + public function testCaptureExceptionHandlesCulpritAsSecondArg() + { + $client = new Dummy_Raven_Client(); + $ex = $this->create_exception(); + $client->captureException($ex, 'test'); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 1); + $event = array_pop($events); + $this->assertEquals($event['culprit'], 'test'); + } + + public function testCaptureExceptionHandlesExcludeOption() + { + $client = new Dummy_Raven_Client(array( + 'exclude' => array('Exception'), + )); + $ex = $this->create_exception(); + $client->captureException($ex, 'test'); + $events = $client->getSentEvents(); + $this->assertEquals(count($events), 0); + } + + public function testDoesRegisterProcessors() + { + $client = new Dummy_Raven_Client(array( + 'processors' => array('Raven_SanitizeDataProcessor'), + )); + $this->assertEquals(count($client->processors), 1); + $this->assertTrue($client->processors[0] instanceof Raven_SanitizeDataProcessor); + } + + public function testProcessDoesCallProcessors() + { + $data = array("key"=>"value"); + + $processor = $this->getMock('Processor', array('process')); + $processor->expects($this->once()) + ->method('process') + ->with($data); + + $client = new Dummy_Raven_Client(); + $client->processors[] = $processor; + $client->process($data); + } + + public function testDefaultProcessorsAreUsed() + { + $client = new Dummy_Raven_Client(); + $defaults = Dummy_Raven_Client::getDefaultProcessors(); + + $this->assertEquals(count($client->processors), count($defaults)); + } + + public function testDefaultProcessorsContainSanitizeDataProcessor() + { + $defaults = Dummy_Raven_Client::getDefaultProcessors(); + + $this->assertTrue(in_array('Raven_SanitizeDataProcessor', $defaults)); + } + + public function testGetDefaultData() + { + $client = new Dummy_Raven_Client(); + $expected = array( + 'platform' => 'php', + 'project' => $client->project, + 'server_name' => $client->name, + 'site' => $client->site, + 'logger' => $client->logger, + 'tags' => $client->tags, + ); + $this->assertEquals($expected, $client->get_default_data()); + } + + /** + * @backupGlobals + */ + public function testGetHttpData() + { + $_SERVER = array( + 'REDIRECT_STATUS' => '200', + 'CONTENT_TYPE' => 'text/xml', + 'CONTENT_LENGTH' => '99', + 'HTTP_HOST' => 'getsentry.com', + 'HTTP_ACCEPT' => 'text/html', + 'HTTP_ACCEPT_CHARSET' => 'utf-8', + 'HTTP_COOKIE' => 'cupcake: strawberry', + 'HTTP_CONTENT_TYPE' => 'text/html', + 'HTTP_CONTENT_LENGTH' => '1000', + 'SERVER_PORT' => '443', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'PATCH', + 'QUERY_STRING' => 'q=bitch&l=en', + 'REQUEST_URI' => '/welcome/', + 'SCRIPT_NAME' => '/index.php', + ); + $_POST = array( + 'stamp' => '1c', + ); + $_COOKIE = array( + 'donut' => 'chocolat', + ); + + $expected = array( + 'sentry.interfaces.Http' => array( + 'method' => 'PATCH', + 'url' => 'https://getsentry.com/welcome/', + 'query_string' => 'q=bitch&l=en', + 'data' => array( + 'stamp' => '1c', + ), + 'cookies' => array( + 'donut' => 'chocolat', + ), + 'headers' => array( + 'Host' => 'getsentry.com', + 'Accept' => 'text/html', + 'Accept-Charset' => 'utf-8', + 'Cookie' => 'cupcake: strawberry', + 'Content-Type' => 'text/xml', + 'Content-Length' => '99', + ), + 'env' => array( + 'REDIRECT_STATUS' => '200', + 'SERVER_PORT' => '443', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'PATCH', + 'QUERY_STRING' => 'q=bitch&l=en', + 'REQUEST_URI' => '/welcome/', + 'SCRIPT_NAME' => '/index.php', + ), + ) + ); + + $client = new Dummy_Raven_Client(); + $this->assertEquals($expected, $client->get_http_data()); + } + + public function testGetUserDataWithSetUser() + { + $client = new Dummy_Raven_Client(); + + $id = 'unique_id'; + $email = 'foo@example.com'; + + $user = array( + 'username' => 'my_user', + ); + + $client->set_user_data($id, $email, $user); + + $expected = array( + 'sentry.interfaces.User' => array( + 'id' => 'unique_id', + 'username' => 'my_user', + 'email' => 'foo@example.com', + ) + ); + + $this->assertEquals($expected, $client->get_user_data()); + } + + public function testGetUserDataWithNoUser() + { + $client = new Dummy_Raven_Client(); + + $expected = array( + 'sentry.interfaces.User' => array( + 'id' => session_id(), + ) + ); + $this->assertEquals($expected, $client->get_user_data()); + } + + public function testGetAuthHeader() + { + $client = new Dummy_Raven_Client(); + + $clientstring = 'raven-php/test'; + $timestamp = '1234341324.340000'; + + $expected = "Sentry sentry_timestamp={$timestamp}, sentry_client={$clientstring}, " . + "sentry_version=" . Dummy_Raven_Client::PROTOCOL . ", " . + "sentry_key=publickey, sentry_secret=secretkey"; + + $this->assertEquals($expected, $client->get_auth_header($timestamp, 'raven-php/test', 'publickey', 'secretkey')); + } + + public function testCaptureMessageWithUserContext() + { + $client = new Dummy_Raven_Client(); + + $client->user_context(array('email' => 'foo@example.com')); + + $client->captureMessage('test'); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals(array( + 'email' => 'foo@example.com', + ), $event['sentry.interfaces.User']); + } + + public function testCaptureMessageWithTagsContext() + { + $client = new Dummy_Raven_Client(); + + $client->tags_context(array('foo' => 'bar')); + $client->tags_context(array('biz' => 'boz')); + $client->tags_context(array('biz' => 'baz')); + + $client->captureMessage('test'); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals(array( + 'foo' => 'bar', + 'biz' => 'baz', + ), $event['tags']); + } + + public function testCaptureMessageWithExtraContext() + { + $client = new Dummy_Raven_Client(); + + $client->extra_context(array('foo' => 'bar')); + $client->extra_context(array('biz' => 'boz')); + $client->extra_context(array('biz' => 'baz')); + + $client->captureMessage('test'); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals(array( + 'foo' => 'bar', + 'biz' => 'baz', + ), $event['extra']); + } + + public function cb1($data) + { + $this->assertEquals('test', $data['message']); + return false; + } + + public function cb2($data) + { + $this->assertEquals('test', $data['message']); + return true; + } + + public function testSendCallback() + { + $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb1'))); + $client->captureMessage('test'); + $events = $client->getSentEvents(); + $this->assertEquals(0, count($events)); + + $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb2'))); + $client->captureMessage('test'); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + } +} diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/CompatTest.php b/htdocs/includes/raven/raven/test/Raven/Tests/CompatTest.php new file mode 100644 index 00000000000..93f7c411c09 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/CompatTest.php @@ -0,0 +1,46 @@ +assertEquals(Raven_Compat::gethostname(), Raven_Compat::_gethostname()); + $this->assertTrue(strlen(Raven_Compat::_gethostname()) > 0); + } + + public function test_hash_hmac() + { + $result = Raven_Compat::hash_hmac('sha1', 'foo', 'bar'); + $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); + + $result = Raven_Compat::_hash_hmac('sha1', 'foo', 'bar'); + $this->assertEquals('85d155c55ed286a300bd1cf124de08d87e914f3a', $result); + } + + public function test_json_encode() + { + $result = Raven_Compat::json_encode(array('foo' => array('bar' => 1))); + $this->assertEquals('{"foo":{"bar":1}}', $result); + + $result = Raven_Compat::_json_encode(array('foo' => array('bar' => 1))); + $this->assertEquals('{"foo":{"bar":1}}', $result); + + $result = Raven_Compat::_json_encode(array(1, 2, 3, 4, 'foo', 'bar')); + $this->assertEquals('[1,2,3,4,"foo","bar"]', $result); + + $result = Raven_Compat::_json_encode(array(1, 'foo', 'foobar' => 'bar')); + $this->assertEquals('{0:1,1:"foo","foobar":"bar"}', $result); + + $result = Raven_Compat::_json_encode(array(array())); + $this->assertEquals('[[]]', $result); + } +} diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/ErrorHandlerTest.php b/htdocs/includes/raven/raven/test/Raven/Tests/ErrorHandlerTest.php new file mode 100644 index 00000000000..656b7593550 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/ErrorHandlerTest.php @@ -0,0 +1,87 @@ +errorLevel = error_reporting(); + } + + public function tearDown() + { + error_reporting($this->errorLevel); + } + + public function testErrorsAreLoggedAsExceptions() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->once()) + ->method('captureException') + ->with($this->isInstanceOf('ErrorException')); + + $handler = new Raven_ErrorHandler($client); + $handler->handleError(E_WARNING, 'message'); + } + + public function testExceptionsAreLogged() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->once()) + ->method('captureException') + ->with($this->isInstanceOf('ErrorException')); + + $e = new ErrorException('message', 0, E_WARNING, '', 0); + + $handler = new Raven_ErrorHandler($client); + $handler->handleException($e); + } + + public function testErrorHandlerCheckSilentReporting() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->never()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(false); + + @trigger_error('Silent', E_USER_WARNING); + } + + public function testErrorHandlerBlockErrorReporting() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->never()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(false); + + error_reporting(E_USER_ERROR); + trigger_error('Warning', E_USER_WARNING); + } + + public function testErrorHandlerPassErrorReportingPass() + { + $client = $this->getMock('Client', array('captureException', 'getIdent')); + $client->expects($this->once()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(false); + + error_reporting(E_USER_WARNING); + trigger_error('Warning', E_USER_WARNING); + } +} diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/SanitizeDataProcessorTest.php b/htdocs/includes/raven/raven/test/Raven/Tests/SanitizeDataProcessorTest.php new file mode 100644 index 00000000000..1b8be37ae78 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/SanitizeDataProcessorTest.php @@ -0,0 +1,180 @@ + array( + 'data' => array( + 'foo' => 'bar', + 'password' => 'hello', + 'the_secret' => 'hello', + 'a_password_here' => 'hello', + 'mypasswd' => 'hello', + 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', + 'card_number' => array( + '1111', + '2222', + '3333', + '4444' + ) + ), + ) + ); + + $client = new Raven_Client(); + $processor = new Raven_SanitizeDataProcessor($client); + $processor->process($data); + + $vars = $data['sentry.interfaces.Http']['data']; + $this->assertEquals($vars['foo'], 'bar'); + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['password']); + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['the_secret']); + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['a_password_here']); + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['mypasswd']); + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['authorization']); + + $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); + + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0']); + } + + public function testDoesFilterCreditCard() + { + $data = array( + 'ccnumba' => '4242424242424242' + ); + + $client = new Raven_Client(); + $processor = new Raven_SanitizeDataProcessor($client); + $processor->process($data); + + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $data['ccnumba']); + } + + /** + * @covers setProcessorOptions + * + */ + public function testSettingProcessorOptions() + { + $client = new Raven_Client(); + $processor = new Raven_SanitizeDataProcessor($client); + + $this->assertEquals($processor->getFieldsRe(), '/(authorization|password|passwd|secret|password_confirmation|card_number|auth_pw)/i', 'got default fields'); + $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){13,16}$/', 'got default values'); + + $options = array( + 'fields_re' => '/(api_token)/i', + 'values_re' => '/^(?:\d[ -]*?){15,16}$/' + ); + + $processor->setProcessorOptions($options); + + $this->assertEquals($processor->getFieldsRe(), '/(api_token)/i', 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), '/^(?:\d[ -]*?){15,16}$/', 'overwrote values'); + } + + /** + * @dataProvider overrideDataProvider + * + * @param $processorOptions + * @param $client_options + * @param $dsn + */ + public function testOverrideOptions($processorOptions, $client_options, $dsn) + { + $client = new Raven_Client($dsn, $client_options); + $processor = $client->processors[0]; + + $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); + } + + /** + * @depends testOverrideOptions + * @dataProvider overrideDataProvider + * + * @param $processorOptions + * @param $client_options + * @param $dsn + */ + public function testOverridenSanitize($processorOptions, $client_options, $dsn) + { + $data = array( + 'sentry.interfaces.Http' => array( + 'data' => array( + 'foo' => 'bar', + 'password' => 'hello', + 'the_secret' => 'hello', + 'a_password_here' => 'hello', + 'mypasswd' => 'hello', + 'api_token' => 'nioenio3nrio3jfny89nby9bhr#RML#R', + 'authorization' => 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', + 'card_number' => array( + '1111111111111111', + '2222', + ) + ), + ) + ); + + $client = new Raven_Client($dsn, $client_options); + $processor = $client->processors[0]; + + $this->assertInstanceOf('Raven_SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_SanitizeDataProcessor']['values_re'], 'overwrote values'); + + $processor->process($data); + + $vars = $data['sentry.interfaces.Http']['data']; + $this->assertEquals($vars['foo'], 'bar', 'did not alter foo'); + $this->assertEquals($vars['password'], 'hello', 'did not alter password'); + $this->assertEquals($vars['the_secret'], 'hello', 'did not alter the_secret'); + $this->assertEquals($vars['a_password_here'], 'hello', 'did not alter a_password_here'); + $this->assertEquals($vars['mypasswd'], 'hello', 'did not alter mypasswd'); + $this->assertEquals($vars['authorization'], 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'did not alter authorization'); + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['api_token'], 'masked api_token'); + + $this->assertEquals(Raven_SanitizeDataProcessor::MASK, $vars['card_number']['0'], 'masked card_number[0]'); + $this->assertEquals($vars['card_number']['1'], $vars['card_number']['1'], 'did not alter card_number[1]'); + } + + /** + * Provides data for testing overriding the processor options + * + * @return array + */ + public static function overrideDataProvider() + { + $processorOptions = array( + 'Raven_SanitizeDataProcessor' => array( + 'fields_re' => '/(api_token)/i', + 'values_re' => '/^(?:\d[ -]*?){15,16}$/' + ) + ); + + $client_options = array( + 'processors' => array('Raven_SanitizeDataProcessor'), + 'processorOptions' => $processorOptions + ); + + $dsn = 'http://9aaa31f9a05b4e72aaa06aa8157a827a:9aa7aa82a9694a08a1a7589a2a035a9a@sentry.domain.tld/1'; + + return array( + array($processorOptions, $client_options, $dsn) + ); + } +} diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/SerializerTest.php b/htdocs/includes/raven/raven/test/Raven/Tests/SerializerTest.php new file mode 100644 index 00000000000..af157130b97 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/SerializerTest.php @@ -0,0 +1,48 @@ +assertEquals(array('1', '2', '3'), $result); + } + + public function testObjectsAreStrings() + { + $input = new Raven_StacktraceTestObject(); + $result = Raven_Serializer::serialize($input); + $this->assertEquals('Object Raven_StacktraceTestObject', $result); + } + + public function testIntsAreInts() + { + $input = 1; + $result = Raven_Serializer::serialize($input); + $this->assertTrue(is_integer($result)); + $this->assertEquals(1, $result); + } + + public function testRecursionMaxDepth() + { + $input = array(); + $input[] = &$input; + $result = Raven_Serializer::serialize($input, 3); + $this->assertEquals(array(array(array('Array of length 1'))), $result); + } +} diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/StacktraceTest.php b/htdocs/includes/raven/raven/test/Raven/Tests/StacktraceTest.php new file mode 100644 index 00000000000..60f97de9585 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/StacktraceTest.php @@ -0,0 +1,223 @@ + 0) { + return call_user_func('raven_test_recurse', $times, $callback); + } + + return call_user_func($callback); +} + +function raven_test_create_stacktrace($args=null, $times=3) +{ + return raven_test_recurse($times, 'debug_backtrace'); +} + +class Raven_Tests_StacktraceTest extends PHPUnit_Framework_TestCase +{ + public function testCanTraceParamContext() + { + $stack = raven_test_create_stacktrace(array('biz', 'baz'), 0); + + $frame = $stack[2]; + $params = Raven_Stacktrace::get_frame_context($frame); + $this->assertEquals($params['args'], array('biz', 'baz')); + $this->assertEquals($params['times'], 0); + } + + public function testSimpleTrace() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + "args"=> array( + "friend", + ), + ), + array( + "file" => dirname(__FILE__) . "/resources/b.php", + "line" => 3, + "args"=> array( + "/tmp/a.php", + ), + "function" => "include_once", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true); + + $frame = $frames[0]; + $this->assertEquals('b.php', $frame["module"]); + $this->assertEquals(3, $frame["lineno"]); + $this->assertNull($frame["function"]); + $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); + $frame = $frames[1]; + $this->assertEquals('a.php', $frame["module"]); + $this->assertEquals(11, $frame["lineno"]); + $this->assertEquals('include_once', $frame["function"]); + $this->assertEquals('a_test($foo);', $frame["context_line"]); + } + + public function testSimpleUnshiftedTrace() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + "args"=> array( + "friend", + ), + ), + array( + "file" => dirname(__FILE__) . "/resources/b.php", + "line" => 3, + "args"=> array( + "/tmp/a.php", + ), + "function" => "include_once", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, false); + + $frame = $frames[0]; + $this->assertEquals('b.php', $frame["module"]); + $this->assertEquals(3, $frame["lineno"]); + $this->assertNull($frame["function"]); + $this->assertEquals('/tmp/a.php', $frame['vars']['param1']); + $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); + $frame = $frames[1]; + $this->assertEquals('a.php', $frame["module"]); + $this->assertEquals(11, $frame["lineno"]); + $this->assertEquals('include_once', $frame["function"]); + $this->assertEquals('friend', $frame['vars']['param1']); + $this->assertEquals('a_test($foo);', $frame["context_line"]); + } + + public function testShiftedCaptureVars() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + "args"=> array( + "friend", + ), + ), + array( + "file" => dirname(__FILE__) . "/resources/b.php", + "line" => 3, + "args"=> array( + "/tmp/a.php", + ), + "function" => "include_once", + ), + ); + + $vars = array( + "foo" => "bar", + "baz" => "zoom" + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, true, $vars); + + $frame = $frames[0]; + $this->assertEquals('b.php', $frame["module"]); + $this->assertEquals(3, $frame["lineno"]); + $this->assertNull($frame["function"]); + $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); + $this->assertFalse(isset($frame['vars'])); + $frame = $frames[1]; + $this->assertEquals('a.php', $frame["module"]); + $this->assertEquals(11, $frame["lineno"]); + $this->assertEquals('include_once', $frame["function"]); + $this->assertEquals('a_test($foo);', $frame["context_line"]); + $this->assertEquals($vars, $frame['vars']); + } + + public function testUnshiftedCaptureVars() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + "args"=> array( + "friend", + ), + ), + array( + "file" => dirname(__FILE__) . "/resources/b.php", + "line" => 3, + "args"=> array( + "/tmp/a.php", + ), + "function" => "include_once", + ), + ); + + $vars = array( + "foo" => "bar", + "baz" => "zoom" + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, false, $vars); + + $frame = $frames[0]; + $this->assertEquals('b.php', $frame["module"]); + $this->assertEquals(3, $frame["lineno"]); + $this->assertNull($frame["function"]); + $this->assertEquals(array('param1' => '/tmp/a.php'), $frame['vars']); + $this->assertEquals("include_once '/tmp/a.php';", $frame["context_line"]); + $frame = $frames[1]; + $this->assertEquals('a.php', $frame["module"]); + $this->assertEquals(11, $frame["lineno"]); + $this->assertEquals('include_once', $frame["function"]); + $this->assertEquals($vars, $frame['vars']); + $this->assertEquals('a_test($foo);', $frame["context_line"]); + } + + public function testDoesFixFrameInfo() + { + /** + * PHP's way of storing backstacks seems bass-ackwards to me + * 'function' is not the function you're in; it's any function being + * called, so we have to shift 'function' down by 1. Ugh. + */ + $stack = raven_test_create_stacktrace(); + + $frames = Raven_Stacktrace::get_stack_info($stack, true); + // just grab the last few frames + $frames = array_slice($frames, -5); + $frame = $frames[0]; + $this->assertEquals('StacktraceTest.php:Raven_Tests_StacktraceTest', $frame['module']); + $this->assertEquals('testDoesFixFrameInfo', $frame['function']); + $frame = $frames[1]; + $this->assertEquals('StacktraceTest.php', $frame['module']); + $this->assertEquals('raven_test_create_stacktrace', $frame['function']); + $frame = $frames[2]; + $this->assertEquals('StacktraceTest.php', $frame['module']); + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[3]; + $this->assertEquals('StacktraceTest.php', $frame['module']); + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[4]; + $this->assertEquals('StacktraceTest.php', $frame['module']); + $this->assertEquals('raven_test_recurse', $frame['function']); + } +} diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/UtilTest.php b/htdocs/includes/raven/raven/test/Raven/Tests/UtilTest.php new file mode 100644 index 00000000000..856b7709755 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/UtilTest.php @@ -0,0 +1,32 @@ + 'bar'); + $result = Raven_Util::get($input, 'baz', 'foo'); + $this->assertEquals('foo', $result); + } + + public function testGetReturnsPresentValuesEvenWhenEmpty() + { + $input = array('foo' => ''); + $result = Raven_Util::get($input, 'foo', 'bar'); + $this->assertEquals('', $result); + } +} diff --git a/htdocs/includes/raven/raven/test/Raven/Tests/resources/a.php b/htdocs/includes/raven/raven/test/Raven/Tests/resources/a.php new file mode 100644 index 00000000000..78ae29c80e3 --- /dev/null +++ b/htdocs/includes/raven/raven/test/Raven/Tests/resources/a.php @@ -0,0 +1,11 @@ +