diff --git a/.gitmodules b/.gitmodules index e8af28a..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "Vendor/sentry"] - path = Vendor/sentry - url = https://github.com/getsentry/sentry-php.git diff --git a/Vendor/sentry b/Vendor/sentry deleted file mode 160000 index c196051..0000000 --- a/Vendor/sentry +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c196051d5c75c45773bb075b1f3bc761f028e2d1 diff --git a/Vendor/sentry/.gitattributes b/Vendor/sentry/.gitattributes new file mode 100644 index 0000000..33a59ae --- /dev/null +++ b/Vendor/sentry/.gitattributes @@ -0,0 +1,3 @@ +/examples export-ignore +/docs export-ignore +/test export-ignore diff --git a/Vendor/sentry/.gitignore b/Vendor/sentry/.gitignore new file mode 100644 index 0000000..eebb99a --- /dev/null +++ b/Vendor/sentry/.gitignore @@ -0,0 +1,7 @@ +*.lock +package.xml +/vendor +.idea +.php_cs.cache +docs/_build +test/clover.xml diff --git a/Vendor/sentry/.gitmodules b/Vendor/sentry/.gitmodules new file mode 100644 index 0000000..1e6464a --- /dev/null +++ b/Vendor/sentry/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/_sentryext"] + path = docs/_sentryext + url = https://github.com/getsentry/sentry-doc-support diff --git a/Vendor/sentry/.php_cs b/Vendor/sentry/.php_cs new file mode 100644 index 0000000..790c396 --- /dev/null +++ b/Vendor/sentry/.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/Vendor/sentry/.scrutinizer.yml b/Vendor/sentry/.scrutinizer.yml new file mode 100644 index 0000000..82b2131 --- /dev/null +++ b/Vendor/sentry/.scrutinizer.yml @@ -0,0 +1,19 @@ +tools: + php_sim: false + php_pdepend: true + php_analyzer: true + php_code_coverage: true + external_code_coverage: + timeout: 2400 # There can be another pull request in progress + runs: 7 # PHP 5.3 + PHP 5.4 + PHP 5.5 + PHP 5.6 + PHP 7.0 + PHP 7.1 + PHP 7.2 + +build: + environment: + php: + version: 5.6.0 + redis: false + postgresql: false + mongodb: false + +filter: + excluded_paths: [vendor/*, test/*, bin/*, docs/*, examples/*] diff --git a/Vendor/sentry/.travis.yml b/Vendor/sentry/.travis.yml new file mode 100644 index 0000000..cf0a4c0 --- /dev/null +++ b/Vendor/sentry/.travis.yml @@ -0,0 +1,53 @@ +language: php +sudo: false + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - nightly +env: + - REMOVE_XDEBUG="0" + - REMOVE_XDEBUG="1" + +matrix: + allow_failures: + - php: hhvm-3.12 + - php: nightly + fast_finish: true + include: + - php: hhvm-3.12 + env: REMOVE_XDEBUG="0" HHVM="1" + - php: 5.3 + env: REMOVE_XDEBUG="0" + dist: precise + - php: 5.3 + env: REMOVE_XDEBUG="1" + dist: precise + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - if [ "$REMOVE_XDEBUG" = "1" ]; then phpenv config-rm xdebug.ini; fi + - composer self-update + +install: travis_retry composer install --no-interaction --prefer-dist + +script: + - composer phpcs + - composer tests-travis + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - if [ $(phpenv version-name) = "5.3" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "5.4" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "5.5" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "5.6" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "7.0" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "7.1" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi + - if [ $(phpenv version-name) = "7.2" ] && [ "$REMOVE_XDEBUG" = "0" ]; then php ocular.phar code-coverage:upload --format=php-clover test/clover.xml --revision=$TRAVIS_COMMIT; fi diff --git a/Vendor/sentry/AUTHORS b/Vendor/sentry/AUTHORS new file mode 100644 index 0000000..675a1e9 --- /dev/null +++ b/Vendor/sentry/AUTHORS @@ -0,0 +1,4 @@ +The Sentry PHP SDK was originally written by Michael van Tellingen +and is maintained by the Sentry Team. + +http://github.com/getsentry/sentry-php/contributors diff --git a/Vendor/sentry/CHANGELOG.md b/Vendor/sentry/CHANGELOG.md new file mode 100644 index 0000000..2eab565 --- /dev/null +++ b/Vendor/sentry/CHANGELOG.md @@ -0,0 +1,189 @@ +# CHANGELOG + +## Unreleased + +- ... + +## 1.8.0 (2017-10-29) + +- Use namespaced classes in test for PHPUnit (#506) +- Prevent segmentation fault on PHP `<5.6` (#504) +- Remove `ini_set` call for unneeded functionality (#501) +- Exclude single `.php` files from the app path (#500) +- Start testing PHP 7.2 (#489) +- Exclude anonymous frames from app path (#482) + +## 1.7.1 (2017-08-02) + +- Fix of filtering sensitive data when there is an exception with multiple 'values' (#483) + +## 1.7.0 (2017-06-07) + +- Corrected some issues with argument serialization in stacktraces (#399). +- The default exception handler will now re-raise exceptions when `call_existing` is true and no exception handler is registered (#421). +- Collect `User.ip_address` automatically (#419). +- Added a processor to remove web cookies. It will be enabled by default in `2.0` (#405). +- Added a processor to remove HTTP body data for POST, PUT, PATCH and DELETE requests. It will be enabled by default in `2.0` (#405). +- Added a processor to sanitize HTTP headers (e.g. the Authorization header) (#428). +- Added a processor to remove `pre_context`, `context_line` and `post_context` informations from reported exceptions (#429). + +## 1.6.2 (2017-02-03) + +- Fixed behavior where fatal errors weren't correctly being reported in most situations. + +## 1.6.1 (2016-12-14) + +- Correct handling of null in `user_context`. + +## 1.6.0 (2016-12-09) + +- Improved serialization of certain types to be more restrictive. +- `error_types` can now be configured via `RavenClient`. +- Class serialization has been expanded to include attributes. +- The session extension is no longer required. +- Monolog is no longer a required dependency. +- `user_context` now merges by default. + +## 1.5.0 (2016-09-29) + +- Added named transaction support. + +## 1.4.0 (2016-09-20) + +This version primarily overhauls the exception/stacktrace generation to fix +a few bugs and improve the quality of data (#359). + +- Added `excluded_app_paths` config. +- Removed `shift_vars` config. +- Correct fatal error handling to only operate on expected types. This also fixes some behavior with the error suppression operator. +- Expose anonymous and similar frames in the stacktrace. +- Default `prefixes` to PHP's include paths. +- Remove `module` usage. +- Better handle empty argument context. +- Correct alignment of filename (current frame) and function (caller frame) + +## 1.3.0 (2016-12-19) + +- Fixed an issue causing the error suppression operator to not be respected (#335) +- Fixed some serialization behavior (#352) +- Fixed an issue with app paths and trailing slashes (#350) +- Handle non-latin encoding with source code context line (#345) + +## 1.2.0 (2016-12-08) + +- Handle non-latin encoding in source code and exception values (#342) +- Ensure pending events are sent on shutdown by default (#338) +- Add `captureLastError` helper (#334) +- Dont report duplicate errors with fatal error handler (#334) +- Enforce maximum length for string serialization (#329) + +## 1.1.0 (2016-07-30) + +- Uncoercable values should no longer prevent exceptions from sending + to the Sentry server. +- `install()` can no longer be called multiple times. + +## 1.0.0 (2016-07-28) + +- Removed deprecated error codes configuration from ErrorHandler. +- Removed env data from HTTP interface. +- Removed `message` attribute from exceptions. +- appPath and prefixes are now resolved fully. +- Fixed various getter methods requiring invalid args. +- Fixed data mutation with `send_callback`. + +## 0.22.0 (2016-06-23) + +- Improve handling of encodings. +- Improve resiliency of variable serialization. +- Add 'formatted' attribute to Message interface. + +## 0.21.0 (2016-06-10) + +- Added `transport` option. +- Added `install()` shortcut. + +## 0.20.0 (2016-06-02) + +- Handle missing function names on frames. +- Remove suppression operator usage in breadcrumbs buffer. +- Force serialization of context values. + +## 0.19.0 (2016-05-27) + +- Add `error_reporting` breadcrumb handler. + +## 0.18.0 (2016-05-17) + +- Remove session from serialized data. +- `send_callback` return value must now be false to prevent capture. +- Add various getter/setter methods for configuration. + +## 0.17.0 (2016-05-11) + +- Don't attempt to serialize fixed SDK inputs. +- Improvements to breadcrumbs support in Monolog. + +## 0.16.0 (2016-05-03) + +- Initial breadcrumbs support with Monolog handler. + +## 0.15.0 (2016-04-29) + +- Fixed some cases where serialization wouldn't happen. +- Added sdk attribute. + +## 0.14.0 (2016-04-27) + +- Added `prefixes` option for stripping absolute paths. +- Removed `abs_path` from stacktraces. +- Added `app_path` to specify application root for resolving `in_app` on frames. +- Moved Laravel support to `sentry-laravel` project. +- Fixed duplicate stack computation. +- Added `dsn` option to ease configuration. +- Fixed an issue with the curl async transport. +- Improved serialization of values. + +## 0.13.0 (2015-09-09) + +- Updated API to use new style interfaces. +- Remove session cookie in default processor. +- Expand docs for Laravel, Symfony2, and Monolog. +- Default error types can now be set as part of ErrorHandler configuration. + +## 0.12.1 (2015-07-26) + +- Dont send empty values for various context. + +## 0.12.0 (2015-05-19) + +- Bumped protocol version to 6. +- Fixed an issue with the async curl handler (GH-216). +- Removed UDP transport. + +## 0.11.0 (2015-03-25) + +- 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 (2014-09-03) + +- Added a default certificate bundle which includes common root CA's as well as getsentry.com's CA. + +## 0.9.1 (2014-08-26) + +- Change default curl connection to `sync` +- Improve CLI reporting + +## 0.9.0 (2014-06-04) + +- Protocol version 5 +- Default to asynchronous HTTP handler using curl_multi. + + +(For previous versions see the commit history) diff --git a/Vendor/sentry/LICENSE b/Vendor/sentry/LICENSE new file mode 100644 index 0000000..9c9f585 --- /dev/null +++ b/Vendor/sentry/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, Sentry, 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. diff --git a/Vendor/sentry/Makefile b/Vendor/sentry/Makefile new file mode 100644 index 0000000..9e2840e --- /dev/null +++ b/Vendor/sentry/Makefile @@ -0,0 +1,21 @@ +.PHONY: test + +develop: update-submodules + composer install --dev + make setup-git + +update-submodules: + git submodule init + git submodule update + +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: cs-dry-run + vendor/bin/phpunit + +setup-git: + git config branch.autosetuprebase always diff --git a/Vendor/sentry/README.md b/Vendor/sentry/README.md new file mode 100644 index 0000000..2f93660 --- /dev/null +++ b/Vendor/sentry/README.md @@ -0,0 +1,162 @@ +

+ + + +

+ +# Sentry for PHP + +[![Build Status](https://secure.travis-ci.org/getsentry/sentry-php.png?branch=master)](http://travis-ci.org/getsentry/sentry-php) +[![Total Downloads](https://img.shields.io/packagist/dt/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Downloads per month](https://img.shields.io/packagist/dm/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Latest stable version](https://img.shields.io/packagist/v/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![License](http://img.shields.io/packagist/l/sentry/sentry.svg?style=flat-square)](https://packagist.org/packages/sentry/sentry) +[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/getsentry/sentry-php/master.svg)](https://scrutinizer-ci.com/g/getsentry/sentry-php/) + +The Sentry PHP error reporter tracks errors and exceptions that happen during the +execution of your application and provides instant notification with detailed +informations needed to prioritize, identify, reproduce and fix each issue. Learn +more about [automatic PHP error reporting with Sentry](https://sentry.io/for/php/). + +## Features + +- Automatically report (un)handled exceptions and errors +- Send customized diagnostic data +- Process and sanitize data before sending it over the network + +## Usage + +```php +// Instantiate a new client with a compatible DSN and install built-in +// handlers +$client = (new Raven_Client('http://public:secret@example.com/1'))->install(); + +// Capture an exception +$event_id = $client->captureException($ex); + +// Give the user feedback +echo "Sorry, there was an error!"; +echo "Your reference ID is " . $event_id; +``` + +For more information, see our [documentation](https://docs.getsentry.com/hosted/clients/php/). + + +## Integration with frameworks + +Other packages exists to integrate this SDK into the most common frameworks. + +### Official integrations + +The following integrations are fully supported and maintained by the Sentry team. + +- [Symfony](https://github.com/getsentry/sentry-symfony) +- [Laravel](https://github.com/getsentry/sentry-laravel) + +### 3rd party integrations + +The following integrations are available and maintained by members of the Sentry community. + +- [Nette](https://github.com/Salamek/raven-nette) +- [ZendFramework](https://github.com/facile-it/sentry-module) +- [WordPress](https://wordpress.org/plugins/wp-sentry-integration/) +- [Drupal](https://www.drupal.org/project/raven) +- [OpenCart](https://github.com/BurdaPraha/oc_sentry) +- ... feel free to be famous, create a port to your favourite platform! + +## Community + +- [Documentation](https://docs.getsentry.com/hosted/clients/php/) +- [Bug Tracker](http://github.com/getsentry/sentry-php/issues) +- [Code](http://github.com/getsentry/sentry-php) +- [Mailing List](https://groups.google.com/group/getsentry) +- [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) + + +Contributing +------------ + +Dependencies are managed through composer: + +``` +$ composer install +``` + +Tests can then be run via phpunit: + +``` +$ vendor/bin/phpunit +``` + + +Tagging a Release +----------------- + +1. Make sure ``CHANGES`` is up to date (add the release date) and ``master`` is green. + +2. Create a new branch for the minor version (if not present): + +``` +$ git checkout -b releases/1.9.x +``` + +3. Update the hardcoded version tag in ``Client.php``: + +``` +class Raven_Client +{ + const VERSION = '1.9.0'; +} +``` + +4. Commit the change: + +``` +$ git commit -a -m "1.9.0" +``` + +5. Tag the branch: + +``` +git tag 1.9.0 +``` + +6. Push the tag: + +``` +git push --tags +``` + +7. Switch back to ``master``: + +``` +git checkout master +``` + +8. Add the next minor release to the ``CHANGES`` file: + +``` +## 1.10.0 (unreleased) +``` + +9. Update the version in ``Client.php``: + +``` +class Raven_Client +{ + const VERSION = '1.10.x-dev'; +} +``` + +10. Lastly, update the composer version in ``composer.json``: + +``` + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + } +``` + +All done! Composer will pick up the tag and configuration automatically. diff --git a/Vendor/sentry/bin/sentry b/Vendor/sentry/bin/sentry new file mode 100755 index 0000000..4694cfc --- /dev/null +++ b/Vendor/sentry/bin/sentry @@ -0,0 +1,94 @@ +#!/usr/bin/env php +getMessage()); + } + + $client = new Raven_Client($dsn, array( + 'trace' => true, + 'curl_method' => 'sync', + 'app_path' => realpath(__DIR__ . '/..'), + 'base_path' => realpath(__DIR__ . '/..'), + )); + + $config = get_object_vars($client); + $required_keys = array('server', '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; + + if (!isset($argv[1])) { + exit('Usage: sentry test '); + } + + $cmd = $argv[1]; + + switch ($cmd) { + case 'test': + cmd_test(@$argv[2]); + break; + default: + exit('Usage: sentry test '); + } +} + +main(); diff --git a/Vendor/sentry/composer.json b/Vendor/sentry/composer.json new file mode 100644 index 0000000..e250dcd --- /dev/null +++ b/Vendor/sentry/composer.json @@ -0,0 +1,59 @@ +{ + "name": "sentry/sentry", + "type": "library", + "description": "A PHP client for Sentry (http://getsentry.com)", + "keywords": ["log", "logging"], + "homepage": "http://getsentry.com", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "David Cramer", + "email": "dcramer@gmail.com" + } + ], + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.8.0", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "monolog/monolog": "*" + }, + "require": { + "php": "^5.3|^7.0", + "ext-curl": "*" + }, + "suggest": { + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", + "monolog/monolog": "Automatically capture Monolog events as breadcrumbs" + }, + "conflict": { + "raven/raven": "*" + }, + "bin": [ + "bin/sentry" + ], + "autoload": { + "psr-0" : { + "Raven_" : "lib/" + } + }, + "scripts": { + "tests": [ + "vendor/bin/phpunit --verbose" + ], + "tests-travis": [ + "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-clover test/clover.xml" + ], + "tests-report": [ + "vendor/bin/phpunit --verbose --configuration phpunit.xml --coverage-html test/html-report" + ], + "phpcs": [ + "vendor/bin/php-cs-fixer fix --config-file=.php_cs --verbose --diff --dry-run" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + } +} diff --git a/Vendor/sentry/docs/Makefile b/Vendor/sentry/docs/Makefile new file mode 100644 index 0000000..60f8b84 --- /dev/null +++ b/Vendor/sentry/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/raven-js.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/raven-js.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/raven-js" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/raven-js" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/Vendor/sentry/docs/conf.py b/Vendor/sentry/docs/conf.py new file mode 100644 index 0000000..04ee267 --- /dev/null +++ b/Vendor/sentry/docs/conf.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# +# sentry-php documentation build configuration file, created by +# sphinx-quickstart on Mon Jan 21 21:04:27 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import re, sys, os, datetime + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'sentry-php' +copyright = u'%s, Functional Software Inc.' % datetime.date.today().year + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# + +with open('../lib/Raven/Client.php') as f: + release = re.search('const VERSION = \'(.*?)\'', f.read()).group(1) + version = '.'.join(release.split('.', 2)[:-1]) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'classic' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'sentry-phpdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'sentry-php.tex', u'sentry-php Documentation', + u'Functional Software Inc.', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'sentry-php', u'sentry-php Documentation', + [u'Functional Software Inc.'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'sentry-php', u'sentry-php Documentation', + u'Functional Software Inc.', 'sentry-php', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +if os.environ.get('SENTRY_FEDERATED_DOCS') != '1': + sys.path.insert(0, os.path.abspath('_sentryext')) + import sentryext + sentryext.activate() diff --git a/Vendor/sentry/docs/config.rst b/Vendor/sentry/docs/config.rst new file mode 100644 index 0000000..ddfbaba --- /dev/null +++ b/Vendor/sentry/docs/config.rst @@ -0,0 +1,276 @@ +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', + )); + +Available Settings +------------------ + +The following settings are available for the client: + +.. describe:: name + + A string to override the default value for the server's hostname. + + Defaults to ``Raven_Compat::gethostname()``. + +.. describe:: tags + + An array of tags to apply to events in this context. + + .. code-block:: php + + 'tags' => array( + 'php_version' => phpversion(), + ) + + .. code-block:: php + + $client->tags_context(array( + 'php_version' => phpversion(), + )); + +.. describe:: release + + The version of your application (e.g. git SHA) + + .. code-block:: php + + 'release' => MyApp::getReleaseVersion(), + + .. code-block:: php + + $client->setRelease(MyApp::getReleaseVersion()); + +.. describe:: environment + + The environment your application is running in. + + .. code-block:: php + + 'environment' => 'production', + + .. code-block:: php + + $client->setEnvironment('production'); + +.. describe:: app_path + + The root path to your application code. + + .. code-block:: php + + 'app_path' => app_root(), + + .. code-block:: php + + $client->setAppPath(app_root()); + +.. describe:: excluded_app_paths + + Paths to exclude from app_path detection. + + .. code-block:: php + + 'excluded_app_paths' => array(app_root() . '/cache'), + + .. code-block:: php + + $client->setExcludedAppPaths(array(app_root() . '/cache')); + +.. describe:: prefixes + + Prefixes which should be stripped from filenames to create relative + paths. + + .. code-block:: php + + 'prefixes' => array( + '/www/php/lib', + ), + + .. code-block:: php + + $client->setPrefixes(array( + '/www/php/lib', + )); + +.. describe:: sample_rate + + The sampling factor to apply to events. A value of 0.00 will deny sending + any events, and a value of 1.00 will send 100% of events. + + .. code-block:: php + + // send 50% of events + 'sample_rate' => 0.5, + +.. describe:: send_callback + + A function which will be called whenever data is ready to be sent. Within + the function you can mutate the data, or alternatively return ``false`` to + instruct the SDK to not send the event. + + .. code-block:: php + + 'send_callback' => function($data) { + // strip HTTP data + @unset($data['request']); + }, + + .. code-block:: php + + $client->setSendCallback(function($data) { + // dont send events if POST + if ($_SERVER['REQUEST_METHOD'] === 'POST') + { + return false; + } + }); + +.. describe:: 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 + +.. describe:: curl_path + + Defaults to 'curl'. + + Specify the path to the curl binary to be used with the 'exec' curl + method. + +.. describe:: transport + + Set a custom transport to override how Sentry events are sent upstream. + + .. code-block:: php + + 'transport' => function($client, $data) { + $myHttpClient->send(array( + 'url' => $client->getServerEndpoint(), + 'method' => 'POST', + 'headers' => array( + 'Content-Encoding' => 'gzip', + 'Content-Type' => 'application/octet-stream', + 'User-Agent' => $client->getUserAgent(), + 'X-Sentry-Auth' => $client->getAuthHeader(), + ), + 'body' => gzipCompress(jsonEncode($data)), + )) + }, + + .. code-block:: php + + $client->setTransport(...); + +.. describe:: trace + + Set this to ``false`` to disable reflection tracing (function calling + arguments) in stacktraces. + + +.. describe:: logger + + Adjust the default logger name for messages. + + Defaults to ``php``. + +.. describe:: 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. + +.. describe:: 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. + +.. describe:: 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. + +.. describe:: processors + + An array of classes to use to process data before it is sent to + Sentry. By default, ``Raven_SanitizeDataProcessor`` is used + +.. describe:: 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}$/' + ) + ) + +.. _sentry-php-request-context: + +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(); diff --git a/Vendor/sentry/docs/index.rst b/Vendor/sentry/docs/index.rst new file mode 100644 index 0000000..9f04540 --- /dev/null +++ b/Vendor/sentry/docs/index.rst @@ -0,0 +1,89 @@ +.. sentry:edition:: self + + Sentry-PHP + ========== + +.. sentry:edition:: on-premise, hosted + + .. class:: platform-php + + PHP + === + +The PHP SDK for Sentry supports PHP 5.3 and higher. It's +available as a BSD licensed Open Source library. + +Installation +------------ + +There are various ways to install the PHP integration for Sentry. The +recommended way is to use `Composer `__:: + + $ composer require "sentry/sentry" + +Alternatively you can manually install it: + +1. Download and extract the latest `sentry-php + `__ archive + to your PHP project. +2. Require the autoloader in your application: + + .. sourcecode:: php + + require_once '/path/to/Raven/library/Raven/Autoloader.php'; + Raven_Autoloader::register(); + +Configuration +------------- + +The most important part is the creation of the raven client. Create it +once and reference it from anywhere you want to interface with Sentry: + +.. code-block:: php + + $client = new Raven_Client('___DSN___'); + +Once you have the client you can either use it manually or enable the +automatic error and exception capturing which is recomended: + +.. code-block:: php + + $error_handler = new Raven_ErrorHandler($client); + $error_handler->registerExceptionHandler(); + $error_handler->registerErrorHandler(); + $error_handler->registerShutdownFunction(); + +Adding Context +-------------- + +Much of the usefulness of Sentry comes from additional context data with +the events. The PHP client makes this very convenient by providing +methods to set thread local context data that is then submitted +automatically with all events. For instance you can use the +``user_context`` method to add information about the current user: + +.. sourcecode:: php + + $client->user_context(array( + 'email' => $USER->getEmail() + )); + +For more information see :ref:`sentry-php-request-context`. + +Deep Dive +--------- + +Want more? Have a look at the full documentation for more information. + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + usage + config + integrations/index + +Resources: + +* `Bug Tracker `_ +* `Github Project `_ diff --git a/Vendor/sentry/docs/integrations/index.rst b/Vendor/sentry/docs/integrations/index.rst new file mode 100644 index 0000000..558d3e5 --- /dev/null +++ b/Vendor/sentry/docs/integrations/index.rst @@ -0,0 +1,9 @@ +Integrations +============ + +.. toctree:: + :maxdepth: 1 + + laravel + monolog + symfony2 diff --git a/Vendor/sentry/docs/integrations/laravel.rst b/Vendor/sentry/docs/integrations/laravel.rst new file mode 100644 index 0000000..acd72a3 --- /dev/null +++ b/Vendor/sentry/docs/integrations/laravel.rst @@ -0,0 +1,315 @@ +Laravel +======= + +Laravel is supported via a native package, `sentry-laravel `_. + +Laravel 5.x +----------- + +Install the ``sentry/sentry-laravel`` package: + +.. code-block:: bash + + $ composer require sentry/sentry-laravel + +If you're on Laravel 5.4 or earlier, you'll need to add the following to your ``config/app.php`` (for Laravel 5.5+ these will be auto-discovered by Laravel): + +.. code-block:: php + + 'providers' => array( + // ... + Sentry\SentryLaravel\SentryLaravelServiceProvider::class, + ) + + 'aliases' => array( + // ... + 'Sentry' => Sentry\SentryLaravel\SentryFacade::class, + ) + + +Add Sentry reporting to ``App/Exceptions/Handler.php``: + +.. code-block:: php + + public function report(Exception $exception) + { + if (app()->bound('sentry') && $this->shouldReport($exception)) { + app('sentry')->captureException($exception); + } + + parent::report($exception); + } + +Create the Sentry configuration file (``config/sentry.php``): + +.. code-block:: bash + + $ php artisan vendor:publish --provider="Sentry\SentryLaravel\SentryLaravelServiceProvider" + + +Add your DSN to ``.env``: + +.. code-block:: bash + + SENTRY_DSN=___DSN___ + +Finally, if you wish to wire up User Feedback, you can do so by creating a custom +error view in `resources/views/errors/500.blade.php`. + +For Laravel 5 up to 5.4 you need to open up ``App/Exceptions/Handler.php`` and extend the +``render`` method to make sure the 500 error is rendered as a view correctly, in 5.5+ this +step is not required anymore an you can skip ahead to the next one: + +.. code-block:: php + + bound('sentry') && $this->shouldReport($exception)) { + app('sentry')->captureException($exception); + } + + parent::report($exception); + } + + public function render($request, Exception $exception) + { + // Convert all non-http exceptions to a proper 500 http exception + // if we don't do this exceptions are shown as a default template + // instead of our own view in resources/views/errors/500.blade.php + if ($this->shouldReport($exception) && !$this->isHttpException($exception) && !config('app.debug')) { + $exception = new HttpException(500, 'Whoops!'); + } + + return parent::render($request, $exception); + } + } + +Next, create ``resources/views/errors/500.blade.php``, and embed the feedback code: + +.. code-block:: html + +
+
Something went wrong.
+ + @if(app()->bound('sentry') && !empty(Sentry::getLastEventID())) +
Error ID: {{ Sentry::getLastEventID() }}
+ + + + + + @endif +
+ +That's it! + +Laravel 4.x +----------- + +Install the ``sentry/sentry-laravel`` package: + +.. code-block:: bash + + $ composer require sentry/sentry-laravel + +Add the Sentry service provider and facade in ``config/app.php``: + +.. code-block:: php + + 'providers' => array( + // ... + 'Sentry\SentryLaravel\SentryLaravelServiceProvider', + ) + + 'aliases' => array( + // ... + 'Sentry' => 'Sentry\SentryLaravel\SentryFacade', + ) + +Create the Sentry configuration file (``config/sentry.php``): + +.. code-block:: php + + $ php artisan config:publish sentry/sentry-laravel + +Add your DSN to ``config/sentry.php``: + +.. code-block:: php + + '___DSN___', + + // ... + ); + +If you wish to wire up Sentry anywhere outside of the standard error handlers, or +if you need to configure additional settings, you can access the Sentry instance +through ``$app``: + +.. code-block:: php + + $app['sentry']->setRelease(Git::sha()); + +Lumen 5.x +--------- + +Install the ``sentry/sentry-laravel`` package: + +.. code-block:: bash + + $ composer require sentry/sentry-laravel + +Register Sentry in ``bootstrap/app.php``: + +.. code-block:: php + + $app->register('Sentry\SentryLaravel\SentryLumenServiceProvider'); + + # Sentry must be registered before routes are included + require __DIR__ . '/../app/Http/routes.php'; + +Add Sentry reporting to ``app/Exceptions/Handler.php``: + +.. code-block:: php + + public function report(Exception $e) + { + if (app()->bound('sentry') && $this->shouldReport($e)) { + app('sentry')->captureException($e); + } + + parent::report($e); + } + +Create the Sentry configuration file (``config/sentry.php``): + +.. code-block:: php + + '___DSN___', + + // capture release as git sha + // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), + ); + +Testing with Artisan +-------------------- + +You can test your configuration using the provided ``artisan`` command: + +.. code-block:: bash + + $ php artisan sentry:test + [sentry] Client configuration: + -> server: https://app.getsentry.com/api/3235/store/ + -> project: 3235 + -> public_key: e9ebbd88548a441288393c457ec90441 + -> secret_key: 399aaee02d454e2ca91351f29bdc3a07 + [sentry] Generating test event + [sentry] Sending test event with ID: 5256614438cf4e0798dc9688e9545d94 + +Adding Context +-------------- + +The mechanism to add context will vary depending on which version of Laravel you're using, but the general approach is the same. Find a good entry point to your application in which the context you want to add is available, ideally early in the process. + +In the following example, we'll use a middleware: + +.. code-block:: php + + namespace App\Http\Middleware; + + use Closure; + + class SentryContext + { + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * + * @return mixed + */ + public function handle($request, Closure $next) + { + if (app()->bound('sentry')) { + /** @var \Raven_Client $sentry */ + $sentry = app('sentry'); + + // Add user context + if (auth()->check()) { + $sentry->user_context([...]); + } else { + $sentry->user_context(['id' => null]); + } + + // Add tags context + $sentry->tags_context([...]); + } + + return $next($request); + } + } + +Configuration +------------- + +The following settings are available for the client: + +.. describe:: dsn + + The DSN to authenticate with Sentry. + + .. code-block:: php + + 'dsn' => '___DSN___', + +.. describe:: release + + The version of your application (e.g. git SHA) + + .. code-block:: php + + 'release' => MyApp::getReleaseVersion(), + + +.. describe:: breadcrumbs.sql_bindings + + Capture bindings on SQL queries. + + Defaults to ``true``. + + .. code-block:: php + + 'breadcrumbs.sql_bindings' => false, + + +.. describe:: user_context + + Capture user_context automatically. + + Defaults to ``true``. + + .. code-block:: php + + 'user_context' => false, + diff --git a/Vendor/sentry/docs/integrations/monolog.rst b/Vendor/sentry/docs/integrations/monolog.rst new file mode 100644 index 0000000..b2d99f8 --- /dev/null +++ b/Vendor/sentry/docs/integrations/monolog.rst @@ -0,0 +1,54 @@ +Monolog +======= + +Capturing Errors +---------------- + +Monolog supports Sentry out of the box, so you'll just need to configure a handler: + +.. sourcecode:: php + + $client = new Raven_Client('___DSN___'); + + $handler = new Monolog\Handler\RavenHandler($client); + $handler->setFormatter(new Monolog\Formatter\LineFormatter("%message% %context% %extra%\n")); + + $monolog->pushHandler($handler); + +Adding Context +-------------- + +Capturing context can be done via a monolog processor: + +.. sourcecode:: php + + $monolog->pushProcessor(function ($record) { + // record the current user + $user = Acme::getCurrentUser(); + $record['context']['user'] = array( + 'name' => $user->getName(), + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + ); + + // Add various tags + $record['context']['tags'] = array('key' => 'value'); + + // Add various generic context + $record['extra']['key'] = 'value'; + + return $record; + }); + + +Breadcrumbs +----------- + +Sentry provides a breadcrumb handler to automatically send logs along as crumbs: + +.. sourcecode:: php + + $client = new Raven_Client('___DSN___'); + + $handler = new \Raven_Breadcrumbs_MonologHandler($client); + $monolog->pushHandler($handler); diff --git a/Vendor/sentry/docs/integrations/symfony2.rst b/Vendor/sentry/docs/integrations/symfony2.rst new file mode 100644 index 0000000..b119164 --- /dev/null +++ b/Vendor/sentry/docs/integrations/symfony2.rst @@ -0,0 +1,42 @@ +Symfony +======= + +Symfony is supported via the `sentry-symfony `_ package as a native bundle. + +Symfony 2+ +---------- + +Install the ``sentry/sentry-symfony`` package: + +.. code-block:: bash + + $ composer require sentry/sentry-symfony + + +Enable the bundle in ``app/AppKernel.php``: + +.. code-block:: php + + ` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\raven-js.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\raven-js.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/Vendor/sentry/docs/sentry-doc-config.json b/Vendor/sentry/docs/sentry-doc-config.json new file mode 100644 index 0000000..fe08543 --- /dev/null +++ b/Vendor/sentry/docs/sentry-doc-config.json @@ -0,0 +1,42 @@ +{ + "support_level": "production", + "platforms": { + "php": { + "name": "PHP", + "type": "language", + "doc_link": "", + "wizard": [ + "index#installation", + "index#configuration", + "usage#capturing-errors" + ] + }, + "php.laravel": { + "name": "Laravel", + "type": "framework", + "doc_link": "integrations/laravel/", + "wizard": [ + "index#installation", + "integrations/laravel#laravel-5-x" + ] + }, + "php.monolog": { + "name": "Monolog", + "type": "framework", + "doc_link": "integrations/monolog/", + "wizard": [ + "index#installation", + "integrations/monolog#monolog" + ] + }, + "php.symfony2": { + "name": "Symfony2", + "type": "framework", + "doc_link": "integrations/symfony2/", + "wizard": [ + "index#installation", + "integrations/symfony2#symfony-2" + ] + } + } +} diff --git a/Vendor/sentry/docs/usage.rst b/Vendor/sentry/docs/usage.rst new file mode 100644 index 0000000..2fd1204 --- /dev/null +++ b/Vendor/sentry/docs/usage.rst @@ -0,0 +1,311 @@ +Usage +===== + +Using Sentry with PHP is straightforward. After installation of the library +you can directly interface with the client and start submitting data. + +Basics +------ + +The most important part is the creation of the raven client. Create it +once and reference it from anywhere you want to interface with Sentry: + +.. code-block:: php + + $sentryClient = new Raven_Client('___DSN___'); + + +Capturing Errors +---------------- + +Sentry includes basic functionality for reporting any uncaught +exceptions or PHP errors. This is done via the error handler, +and appropriate hooks for each of PHP's built-in reporting: + +.. code-block:: php + + $error_handler = new Raven_ErrorHandler($sentryClient); + $error_handler->registerExceptionHandler(); + $error_handler->registerErrorHandler(); + $error_handler->registerShutdownFunction(); + +.. note:: Calling ``install()`` on a Raven_Client instance will automatically + register these handlers. + + +Reporting Exceptions +-------------------- + +If you want to report exceptions manually you can use the +`captureException` function. + +.. code-block:: php + + // Basic Reporting + $sentryClient->captureException($ex); + + // Provide some additional data with an exception + $sentryClient->captureException($ex, array( + 'extra' => array( + 'php_version' => phpversion() + ), + )); + + +Reporting Other Errors +---------------------- + +Sometimes you don't have an actual exception object, but something bad happened and you +want to report it anyways. This is where `captureMessage` comes in. It +takes a message and reports it to sentry. + +.. code-block:: php + + // Capture a message + $sentryClient->captureMessage('my log message'); + +Note, ``captureMessage`` has a slightly different API than ``captureException`` to support +parameterized formatting: + +.. code-block:: php + + $sentryClient->captureMessage('my %s message', array('log'), array( + 'extra' => array( + 'foo' => 'bar', + ), + )); + + +Optional Attributes +------------------- + +With calls to ``captureException`` or ``captureMessage`` additional data +can be supplied: + +.. code-block:: php + + $sentryClient->captureException($ex, array( + 'attr' => 'value', + )); + + +.. describe:: extra + +Additional context for this event. Must be a mapping. Children can be any native JSON type. + +.. code-block:: php + + array( + 'extra' => array('key' => 'value') + ) + +.. describe:: fingerprint + +The fingerprint for grouping this event. + +.. code-block:: php + + array( + 'fingerprint' => ['{{ default }}', 'other value'] + ) + +.. describe:: level + +The level of the event. Defaults to ``error``. + +.. code-block:: php + + array( + 'level' => 'warning' + ) + +Sentry is aware of the following levels: + +* debug (the least serious) +* info +* warning +* error +* fatal (the most serious) + +.. describe:: logger + +The logger name for the event. + +.. code-block:: php + + array( + 'logger' => 'default' + ) + +.. describe:: tags + +Tags to index with this event. Must be a mapping of strings. + +.. code-block:: php + + array( + 'tags' => array('key' => 'value') + ) + +.. describe:: user + +The acting user. + +.. code-block:: php + + array( + 'user' => array( + 'id' => 42, + 'email' => 'clever-girl' + ) + ) + +Getting Back an Event ID +------------------------ + +An event id is a globally unique id for the event that was just sent. This +event id can be used to find the exact event from within Sentry. + +This is often used to display for the user and report an error to customer +service. + +.. code-block:: php + + $sentryClient->getLastEventID(); + +.. _php-user-feedback: + +User Feedback +------------- + +To enable user feedback for crash reports you will need to create an error handler +which is aware of the last event ID. + +.. sourcecode:: php + + captureException($exc); + + return $this->render('500.html', array( + 'sentry_event_id' => $event_id, + ), 500); + } + } + +Then in your template you can load up the feedback widget: + +.. sourcecode:: html+django + + + + + {% if sentry_event_id %} + + {% endif %} + +That's it! + +For more details on this feature, see the :doc:`User Feedback guide <../../../learn/user-feedback>`. + + +Handling Failures +----------------- + +The SDK attempts to minimize failures, and when they happen will always try to avoid bubbling them up +to your application. If you do want to know when an event fails to record, you can use the ``getLastError`` +helper: + +.. code-block:: php + + if ($sentryClient->getLastError() !== null) { + echo "Something went very, very wrong"; + // $sentryClient->getLastError() contains the error that occurred + } else { + // Give the user feedback + echo "Sorry, there was an error!"; + echo "Your reference ID is " . $event_id; + } + + +Breadcrumbs +----------- + +Sentry supports capturing breadcrumbs -- events that happened prior to an issue. + +.. code-block:: php + + $sentryClient->breadcrumbs->record(array( + 'message' => 'Authenticating user as ' . $username, + 'category' => 'auth', + 'level' => 'info', + )); + + +Filtering Out Errors +-------------------- + +Its common that you might want to prevent automatic capture of certain areas. Ideally you simply would avoid calling out to Sentry in that case, but that's often easier said than done. Instead, you can provide a function which the SDK will call before it sends any data, allowing you both to mutate that data, as well as prevent it from being sent to the server. + +.. code-block:: php + + $sentryClient->setSendCallback(function($data) { + $ignore_types = array('Symfony\Component\HttpKernel\Exception\NotFoundHttpException'); + + if (isset($data['exception']) && in_array($data['exception']['values'][0]['type'], $ignore_types)) + { + return false; + } + }); + + +Error Control Operators +----------------------- + +In PHP its fairly common to use the `suppression operator `_ +to avoid bubbling up handled errors: + +.. code-block:: php + + $my_file = @file('non_existent_file'); + +In these situations, Sentry will never capture the error. If you wish to capture it at that stage +you'd need to manually call out to the PHP client: + +.. code-block:: php + + $my_file = @file('non_existent_file'); + if (!$my_file) { + // ... + $sentryClient->captureLastError(); + } + + +Testing Your Connection +----------------------- + +The PHP client includes a simple helper script to test your connection and +credentials with the Sentry master server:: + + $ bin/sentry test ___DSN___ + Client configuration: + -> server: [___API_URL___] + -> project: ___PROJECT_ID___ + -> public_key: ___PUBLIC_KEY___ + -> secret_key: ___SECRET_KEY___ + + Sending a test event: + -> event ID: f1765c9aed4f4ceebe5a93df9eb2d34f + + Done! diff --git a/Vendor/sentry/examples/vanilla/README.md b/Vendor/sentry/examples/vanilla/README.md new file mode 100644 index 0000000..5a32cd6 --- /dev/null +++ b/Vendor/sentry/examples/vanilla/README.md @@ -0,0 +1,6 @@ +Running this example: + +``` +# Run webserver +php -S localhost:8000 +``` diff --git a/Vendor/sentry/examples/vanilla/index.php b/Vendor/sentry/examples/vanilla/index.php new file mode 100644 index 0000000..c96c0af --- /dev/null +++ b/Vendor/sentry/examples/vanilla/index.php @@ -0,0 +1,39 @@ +setAppPath(__DIR__) + ->setRelease(Raven_Client::VERSION) + ->setPrefixes(array(__DIR__)) + ->install(); +} + +function createCrumbs() +{ + echo($undefined['foobar']); + echo($undefined['bizbaz']); +} + +function createError() +{ + 1 / 0; +} + + +function createException() +{ + throw new Exception('example exception'); +} + +setupSentry(); +createCrumbs(); +createError(); +createException(); diff --git a/Vendor/sentry/lib/Raven/Autoloader.php b/Vendor/sentry/lib/Raven/Autoloader.php new file mode 100644 index 0000000..bbf4f76 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Autoloader.php @@ -0,0 +1,44 @@ +size = $size; + $this->reset(); + } + + public function reset() + { + $this->count = 0; + $this->pos = 0; + $this->buffer = array(); + } + + public function record($crumb) + { + if (empty($crumb['timestamp'])) { + $crumb['timestamp'] = microtime(true); + } + $this->buffer[$this->pos] = $crumb; + $this->pos = ($this->pos + 1) % $this->size; + $this->count++; + } + + /** + * @return array[] + */ + public function fetch() + { + $results = array(); + for ($i = 0; $i <= ($this->size - 1); $i++) { + $idx = ($this->pos + $i) % $this->size; + if (isset($this->buffer[$idx])) { + $results[] = $this->buffer[$idx]; + } + } + return $results; + } + + public function is_empty() + { + return $this->count === 0; + } + + public function to_json() + { + return array( + 'values' => $this->fetch(), + ); + } +} diff --git a/Vendor/sentry/lib/Raven/Breadcrumbs/ErrorHandler.php b/Vendor/sentry/lib/Raven/Breadcrumbs/ErrorHandler.php new file mode 100644 index 0000000..ffb07fd --- /dev/null +++ b/Vendor/sentry/lib/Raven/Breadcrumbs/ErrorHandler.php @@ -0,0 +1,45 @@ +ravenClient = $ravenClient; + } + + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + $this->ravenClient->breadcrumbs->record(array( + 'category' => 'error_reporting', + 'message' => $message, + 'level' => $this->ravenClient->translateSeverity($code), + 'data' => array( + 'code' => $code, + 'line' => $line, + 'file' => $file, + ), + )); + + if ($this->existingHandler !== null) { + return call_user_func($this->existingHandler, $code, $message, $file, $line, $context); + } else { + return false; + } + } + + public function install() + { + $this->existingHandler = set_error_handler(array($this, 'handleError'), E_ALL); + return $this; + } +} diff --git a/Vendor/sentry/lib/Raven/Breadcrumbs/MonologHandler.php b/Vendor/sentry/lib/Raven/Breadcrumbs/MonologHandler.php new file mode 100644 index 0000000..52a658d --- /dev/null +++ b/Vendor/sentry/lib/Raven/Breadcrumbs/MonologHandler.php @@ -0,0 +1,101 @@ + Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + protected $excMatch = '/^exception \'([^\']+)\' with message \'(.+)\' in .+$/s'; + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @param Raven_Client $ravenClient + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * @param string $message + * @return array|null + */ + protected function parseException($message) + { + if (preg_match($this->excMatch, $message, $matches)) { + return array($matches[1], $matches[2]); + } + + return null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // sentry uses the 'nobreadcrumb' attribute to skip reporting + if (!empty($record['context']['nobreadcrumb'])) { + return; + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + /** + * @var Exception $exc + */ + $exc = $record['context']['exception']; + $crumb = array( + 'type' => 'error', + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'data' => array( + 'type' => get_class($exc), + 'value' => $exc->getMessage(), + ), + ); + } else { + // TODO(dcramer): parse exceptions out of messages and format as above + if ($error = $this->parseException($record['message'])) { + $crumb = array( + 'type' => 'error', + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'data' => array( + 'type' => $error[0], + 'value' => $error[1], + ), + ); + } else { + $crumb = array( + 'level' => $this->logLevels[$record['level']], + 'category' => $record['channel'], + 'message' => $record['message'], + ); + } + } + + $this->ravenClient->breadcrumbs->record($crumb); + } +} diff --git a/Vendor/sentry/lib/Raven/Client.php b/Vendor/sentry/lib/Raven/Client.php new file mode 100644 index 0000000..5e863b9 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Client.php @@ -0,0 +1,1450 @@ +logger = Raven_Util::get($options, 'logger', 'php'); + $this->server = Raven_Util::get($options, 'server'); + $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', self::_server_variable('SERVER_NAME')); + $this->tags = Raven_Util::get($options, 'tags', array()); + $this->release = Raven_Util::get($options, 'release', null); + $this->environment = Raven_Util::get($options, 'environment', null); + $this->sample_rate = Raven_Util::get($options, 'sample_rate', 1); + $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->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', static::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->trust_x_forwarded_proto = Raven_Util::get($options, 'trust_x_forwarded_proto'); + $this->transport = Raven_Util::get($options, 'transport', null); + $this->mb_detect_order = Raven_Util::get($options, 'mb_detect_order', null); + $this->error_types = Raven_Util::get($options, 'error_types', null); + + // app path is used to determine if code is part of your application + $this->setAppPath(Raven_Util::get($options, 'app_path', null)); + $this->setExcludedAppPaths(Raven_Util::get($options, 'excluded_app_paths', null)); + // a list of prefixes used to coerce absolute paths into relative + $this->setPrefixes(Raven_Util::get($options, 'prefixes', static::getDefaultPrefixes())); + $this->processors = $this->setProcessorsFromOptions($options); + + $this->_lasterror = null; + $this->_last_sentry_error = null; + $this->_curl_instance = null; + $this->_last_event_id = null; + $this->_user = null; + $this->_pending_events = array(); + $this->context = new Raven_Context(); + $this->breadcrumbs = new Raven_Breadcrumbs(); + $this->_shutdown_function_has_been_set = false; + + $this->sdk = Raven_Util::get($options, 'sdk', array( + 'name' => 'sentry-php', + 'version' => self::VERSION, + )); + $this->serializer = new Raven_Serializer($this->mb_detect_order); + $this->reprSerializer = new Raven_ReprSerializer($this->mb_detect_order); + + if ($this->curl_method == 'async') { + $this->_curl_handler = new Raven_CurlHandler($this->get_curl_options()); + } + + $this->transaction = new Raven_TransactionStack(); + if (static::is_http_request() && isset($_SERVER['PATH_INFO'])) { + // @codeCoverageIgnoreStart + $this->transaction->push($_SERVER['PATH_INFO']); + // @codeCoverageIgnoreEnd + } + + if (Raven_Util::get($options, 'install_default_breadcrumb_handlers', true)) { + $this->registerDefaultBreadcrumbHandlers(); + } + + if (Raven_Util::get($options, 'install_shutdown_handler', true)) { + $this->registerShutdownFunction(); + } + } + + public function __destruct() + { + // Force close curl resource + $this->close_curl_resource(); + } + + /** + * Destruct all objects contain link to this object + * + * This method can not delete shutdown handler + */ + public function close_all_children_link() + { + $this->processors = array(); + } + + /** + * Installs any available automated hooks (such as error_reporting). + */ + public function install() + { + if ($this->error_handler) { + throw new Raven_Exception(sprintf('%s->install() must only be called once', get_class($this))); + } + $this->error_handler = new Raven_ErrorHandler($this, false, $this->error_types); + $this->error_handler->registerExceptionHandler(); + $this->error_handler->registerErrorHandler(); + $this->error_handler->registerShutdownFunction(); + return $this; + } + + public function getRelease() + { + return $this->release; + } + + public function setRelease($value) + { + $this->release = $value; + return $this; + } + + public function getEnvironment() + { + return $this->environment; + } + + public function setEnvironment($value) + { + $this->environment = $value; + return $this; + } + + private static function getDefaultPrefixes() + { + $value = get_include_path(); + return explode(PATH_SEPARATOR, $value); + } + + private static function _convertPath($value) + { + $path = @realpath($value); + if ($path === false) { + $path = $value; + } + // we need app_path to have a trailing slash otherwise + // base path detection becomes complex if the same + // prefix is matched + if ($path{0} === DIRECTORY_SEPARATOR && substr($path, -1) !== DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + return $path; + } + + public function getAppPath() + { + return $this->app_path; + } + + public function setAppPath($value) + { + if ($value) { + $this->app_path = static::_convertPath($value); + } else { + $this->app_path = null; + } + return $this; + } + + public function getExcludedAppPaths() + { + return $this->excluded_app_paths; + } + + public function setExcludedAppPaths($value) + { + if ($value) { + $excluded_app_paths = array(); + + // We should be able to exclude a php files + foreach ((array) $value as $path) { + $excluded_app_paths[] = substr($path, -4) !== '.php' ? self::_convertPath($path) : $path; + } + } else { + $excluded_app_paths = null; + } + + $this->excluded_app_paths = $excluded_app_paths; + + return $this; + } + + public function getPrefixes() + { + return $this->prefixes; + } + + /** + * @param array $value + * @return Raven_Client + */ + public function setPrefixes($value) + { + $this->prefixes = $value ? array_map(array($this, '_convertPath'), $value) : $value; + return $this; + } + + public function getSendCallback() + { + return $this->send_callback; + } + + public function setSendCallback($value) + { + $this->send_callback = $value; + return $this; + } + + public function getTransport() + { + return $this->transport; + } + + public function getServerEndpoint($value = '') + { + return $this->server; + } + + public static function getUserAgent() + { + return 'sentry-php/' . self::VERSION; + } + + /** + * Set a custom transport to override how Sentry events are sent upstream. + * + * The bound function will be called with ``$client`` and ``$data`` arguments + * and is responsible for encoding the data, authenticating, and sending + * the data to the upstream Sentry server. + * + * @param Callable $value Function to be called + * @return Raven_Client + */ + public function setTransport($value) + { + $this->transport = $value; + return $this; + } + + /** + * @return string[]|Raven_Processor[] + */ + public static function getDefaultProcessors() + { + return array( + 'Raven_Processor_SanitizeDataProcessor', + ); + } + + /** + * Sets the Raven_Processor sub-classes to be used when data is processed before being + * sent to Sentry. + * + * @param $options + * @return Raven_Processor[] + */ + public function setProcessorsFromOptions($options) + { + $processors = array(); + foreach (Raven_util::get($options, 'processors', static::getDefaultProcessors()) as $processor) { + /** + * @var Raven_Processor $new_processor + * @var Raven_Processor|string $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 + * @return array parsed DSN + * + * @doc http://raven.readthedocs.org/en/latest/config/#the-sentry-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( + 'server' => 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. + * + * @param mixed $ident + * @return mixed + * @codeCoverageIgnore + */ + public function getIdent($ident) + { + // XXX: We don't calculate checksums yet, so we only have the ident. + return $ident; + } + + /** + * @param string $message The message (primary description) for the event. + * @param array $params params to use when formatting the message. + * @param string $level Log level group + * @param bool|array $stack + * @param mixed $vars + * @return string|null + * @deprecated + * @codeCoverageIgnore + */ + public function message($message, $params = array(), $level = self::INFO, + $stack = false, $vars = null) + { + return $this->captureMessage($message, $params, $level, $stack, $vars); + } + + /** + * @param Exception $exception + * @return string|null + * @deprecated + * @codeCoverageIgnore + */ + public function exception($exception) + { + return $this->captureException($exception); + } + + /** + * Log a message to sentry + * + * @param string $message The message (primary description) for the event. + * @param array $params params to use when formatting the message. + * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param bool|array $stack + * @param mixed $vars + * @return string|null + */ + public function captureMessage($message, $params = array(), $data = 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 ($data === null) { + $data = array(); + // support legacy method of passing in a level name as the third arg + } elseif (!is_array($data)) { + $data = array( + 'level' => $data, + ); + } + + $data['message'] = $formatted_message; + $data['sentry.interfaces.Message'] = array( + 'message' => $message, + 'params' => $params, + 'formatted' => $formatted_message, + ); + + return $this->capture($data, $stack, $vars); + } + + /** + * Log an exception to sentry + * + * @param Exception $exception The Exception object. + * @param array $data Additional attributes to pass with this event (see Sentry docs). + * @param mixed $logger + * @param mixed $vars + * @return string|null + */ + public function captureException($exception, $data = 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 ($data === null) { + $data = array(); + } + + $exc = $exception; + do { + $exc_data = array( + 'value' => $this->serializer->serialize($exc->getMessage()), + 'type' => get_class($exc), + ); + + /**'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')) { + // @codeCoverageIgnoreStart + spl_autoload_call('Raven_Stacktrace'); + // @codeCoverageIgnoreEnd + } + + $exc_data['stacktrace'] = array( + 'frames' => Raven_Stacktrace::get_stack_info( + $trace, $this->trace, $vars, $this->message_limit, $this->prefixes, + $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer + ), + ); + + $exceptions[] = $exc_data; + } while ($has_chained_exceptions && $exc = $exc->getPrevious()); + + $data['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); + } + + + /** + * Capture the most recent error (obtained with ``error_get_last``). + * @return string|null + */ + public function captureLastError() + { + if (null === $error = error_get_last()) { + return null; + } + + $e = new ErrorException( + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] + ); + + return $this->captureException($e); + } + + /** + * Log an query to sentry + * + * @param string|null $query + * @param string $level + * @param string $engine + */ + 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); + } + + /** + * Return the last captured event's ID or null if none available. + */ + public function getLastEventID() + { + return $this->_last_event_id; + } + + protected function registerDefaultBreadcrumbHandlers() + { + $handler = new Raven_Breadcrumbs_ErrorHandler($this); + $handler->install(); + } + + protected function registerShutdownFunction() + { + if (!$this->_shutdown_function_has_been_set) { + $this->_shutdown_function_has_been_set = true; + register_shutdown_function(array($this, 'onShutdown')); + } + } + + /** + * @return bool + * @codeCoverageIgnore + */ + protected static function is_http_request() + { + return isset($_SERVER['REQUEST_METHOD']) && PHP_SAPI !== 'cli'; + } + + protected function get_http_data() + { + $headers = array(); + + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $header_key = + str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); + $headers[$header_key] = $value; + } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { + $header_key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); + $headers[$header_key] = $value; + } + } + + $result = array( + 'method' => self::_server_variable('REQUEST_METHOD'), + 'url' => $this->get_current_url(), + 'query_string' => self::_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; + } + + return array( + 'request' => $result, + ); + } + + protected function get_user_data() + { + $user = $this->context->user; + if ($user === null) { + if (!function_exists('session_id') || !session_id()) { + return array(); + } + $user = array( + 'id' => session_id(), + ); + if (!empty($_SERVER['REMOTE_ADDR'])) { + $user['ip_address'] = $_SERVER['REMOTE_ADDR']; + } + if (!empty($_SESSION)) { + $user['data'] = $_SESSION; + } + } + return array( + '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', + 'sdk' => $this->sdk, + 'culprit' => $this->transaction->peek(), + ); + } + + public function capture($data, $stack = null, $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'] = static::uuid4(); + } + + if (isset($data['message'])) { + $data['message'] = substr($data['message'], 0, $this->message_limit); + } + + $data = array_merge($this->get_default_data(), $data); + + if (static::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; + } + if ($this->environment) { + $data['environment'] = $this->environment; + } + + $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 (empty($data['extra'])) { + unset($data['extra']); + } + if (empty($data['tags'])) { + unset($data['tags']); + } + if (empty($data['user'])) { + unset($data['user']); + } + if (empty($data['request'])) { + unset($data['request']); + } + + if (!$this->breadcrumbs->is_empty()) { + $data['breadcrumbs'] = $this->breadcrumbs->fetch(); + } + + 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')) { + // @codeCoverageIgnoreStart + spl_autoload_call('Raven_Stacktrace'); + // @codeCoverageIgnoreEnd + } + + if (!isset($data['stacktrace']) && !isset($data['exception'])) { + $data['stacktrace'] = array( + 'frames' => Raven_Stacktrace::get_stack_info( + $stack, $this->trace, $vars, $this->message_limit, $this->prefixes, + $this->app_path, $this->excluded_app_paths, $this->serializer, $this->reprSerializer + ), + ); + } + } + + $this->sanitize($data); + $this->process($data); + + if (!$this->store_errors_for_bulk_send) { + $this->send($data); + } else { + $this->_pending_events[] = $data; + } + + $this->_last_event_id = $data['event_id']; + + return $data['event_id']; + } + + public function sanitize(&$data) + { + // attempt to sanitize any user provided data + if (!empty($data['request'])) { + $data['request'] = $this->serializer->serialize($data['request']); + } + if (!empty($data['user'])) { + $data['user'] = $this->serializer->serialize($data['user'], 3); + } + if (!empty($data['extra'])) { + $data['extra'] = $this->serializer->serialize($data['extra']); + } + if (!empty($data['tags'])) { + foreach ($data['tags'] as $key => $value) { + $data['tags'][$key] = @(string)$value; + } + } + if (!empty($data['contexts'])) { + $data['contexts'] = $this->serializer->serialize($data['contexts'], 5); + } + } + + /** + * 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() + { + foreach ($this->_pending_events as $data) { + $this->send($data); + } + $this->_pending_events = array(); + 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'); + } + } + + /** + * @param array $data + * @return string|bool + */ + public function encode(&$data) + { + $message = Raven_Compat::json_encode($data); + if ($message === false) { + if (function_exists('json_last_error_msg')) { + $this->_lasterror = json_last_error_msg(); + } else { + // @codeCoverageIgnoreStart + $this->_lasterror = json_last_error(); + // @codeCoverageIgnoreEnd + } + return false; + } + + if (function_exists("gzcompress")) { + $message = gzcompress($message); + } + + // PHP's builtin curl_* function are happy without this, but the exec method requires it + $message = base64_encode($message); + + return $message; + } + + /** + * Wrapper to handle encoding and sending data to the Sentry API server. + * + * @param array $data Associative array of data to log + */ + public function send(&$data) + { + if (is_callable($this->send_callback) + && call_user_func_array($this->send_callback, array(&$data)) === false + ) { + // if send_callback returns false, end native send + return; + } + + if (!$this->server) { + return; + } + + if ($this->transport) { + call_user_func($this->transport, $this, $data); + return; + } + + // should this event be sampled? + if (rand(1, 100) / 100.0 > $this->sample_rate) { + return; + } + + $message = $this->encode($data); + + $headers = array( + 'User-Agent' => static::getUserAgent(), + 'X-Sentry-Auth' => $this->getAuthHeader(), + 'Content-Type' => 'application/octet-stream' + ); + + $this->send_remote($this->server, $message, $headers); + } + + /** + * Send data to Sentry + * + * @param string $url Full URL to Sentry + * @param array|string $data Associative array of data to log + * @param array $headers Associative array of headers + */ + protected 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 static function get_default_ca_cert() + { + return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cacert.pem'; + } + + /** + * @return array + * @doc http://stackoverflow.com/questions/9062798/php-curl-timeout-is-not-working/9063006#9063006 + */ + 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 => 'sentry-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 stackoverflow link in the phpdoc + 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|string $data Associative array of data to log + * @param array $headers Associative array of headers + */ + protected 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); + } + } + + protected function buildCurlCommand($url, $data, $headers) + { + // TODO(dcramer): support ca_cert + $cmd = $this->curl_path.' -X POST '; + foreach ($headers as $key => $value) { + $cmd .= '-H ' . escapeshellarg($key.': '.$value). ' '; + } + $cmd .= '-d ' . escapeshellarg($data) . ' '; + $cmd .= escapeshellarg($url) . ' '; + $cmd .= '-m 5 '; // 5 second timeout for the whole process (connect + send) + if (!$this->verify_ssl) { + $cmd .= '-k '; + } + $cmd .= '> /dev/null 2>&1 &'; // ensure exec returns immediately while curl runs in the background + + return $cmd; + } + + /** + * 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|string $data Associative array of data to log + * @param array $headers Associative array of headers + * @return bool + */ + protected function send_http_asynchronous_curl_exec($url, $data, $headers) + { + exec($this->buildCurlCommand($url, $data, $headers)); + 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|string $data Associative array of data to log + * @param array $headers Associative array of headers + * @return bool + */ + protected 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:'; + + if (is_null($this->_curl_instance)) { + $this->_curl_instance = curl_init($url); + } + curl_setopt($this->_curl_instance, CURLOPT_POST, 1); + curl_setopt($this->_curl_instance, CURLOPT_HTTPHEADER, $new_headers); + curl_setopt($this->_curl_instance, CURLOPT_POSTFIELDS, $data); + curl_setopt($this->_curl_instance, CURLOPT_RETURNTRANSFER, true); + + $options = $this->get_curl_options(); + if (isset($options[CURLOPT_CAINFO])) { + $ca_cert = $options[CURLOPT_CAINFO]; + unset($options[CURLOPT_CAINFO]); + } else { + $ca_cert = null; + } + curl_setopt_array($this->_curl_instance, $options); + + $buffer = curl_exec($this->_curl_instance); + + $errno = curl_errno($this->_curl_instance); + // CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE + if ((($errno == 60) || ($errno == 77)) && !is_null($ca_cert)) { + curl_setopt($this->_curl_instance, CURLOPT_CAINFO, $ca_cert); + $buffer = curl_exec($this->_curl_instance); + } + if ($errno != 0) { + $this->_lasterror = curl_error($this->_curl_instance); + $this->_last_sentry_error = null; + return false; + } + + $code = curl_getinfo($this->_curl_instance, CURLINFO_HTTP_CODE); + $success = ($code == 200); + if ($success) { + $this->_lasterror = null; + $this->_last_sentry_error = null; + } else { + // It'd be nice just to raise an exception here, but it's not very PHP-like + $this->_lasterror = curl_error($this->_curl_instance); + $this->_last_sentry_error = @json_decode($buffer); + } + + 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 static 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)); + } + + public function getAuthHeader() + { + $timestamp = microtime(true); + return $this->get_auth_header( + $timestamp, static::getUserAgent(), $this->public_key, $this->secret_key + ); + } + + /** + * Generate an uuid4 value + * + * @return string + */ + protected static 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 + */ + protected function get_current_url() + { + // When running from commandline the REQUEST_URI is missing. + if (!isset($_SERVER['REQUEST_URI'])) { + return null; + } + + // 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'] : ''))); + + $httpS = $this->isHttps() ? 's' : ''; + return "http{$httpS}://{$host}{$_SERVER['REQUEST_URI']}"; + } + + /** + * Was the current request made over https? + * + * @return bool + */ + protected function isHttps() + { + if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { + return true; + } + + if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) { + return true; + } + + if (!empty($this->trust_x_forwarded_proto) && + !empty($_SERVER['X-FORWARDED-PROTO']) && + $_SERVER['X-FORWARDED-PROTO'] === 'https') { + return true; + } + + return false; + } + + /** + * Get the value of a key from $_SERVER + * + * @param string $key Key whose value you wish to obtain + * @return string Key's value + */ + private static 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 + * + * @deprecated + * @param string $id User's ID + * @param string|null $email User's email + * @param array $data Additional user data + * @codeCoverageIgnore + */ + public function set_user_data($id, $email = null, $data = array()) + { + $user = array('id' => $id); + if (isset($email)) { + $user['email'] = $email; + } + $this->user_context(array_merge($user, $data)); + } + + public function onShutdown() + { + if (!defined('RAVEN_CLIENT_END_REACHED')) { + define('RAVEN_CLIENT_END_REACHED', true); + } + $this->sendUnsentErrors(); + if ($this->curl_method == 'async') { + $this->_curl_handler->join(); + } + } + + /** + * Sets user context. + * + * @param array $data Associative array of user data + * @param bool $merge Merge existing context with new context + */ + public function user_context($data, $merge = true) + { + if ($merge && $this->context->user !== null) { + // bail if data is null + if (!$data) { + return; + } + $this->context->user = array_merge($this->context->user, $data); + } else { + $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; + } + + /** + * @return object|null + */ + public function getLastSentryError() + { + return $this->_last_sentry_error; + } + + /** + * @return bool + */ + public function getShutdownFunctionHasBeenSet() + { + return $this->_shutdown_function_has_been_set; + } + + public function close_curl_resource() + { + if (!is_null($this->_curl_instance)) { + curl_close($this->_curl_instance); + $this->_curl_instance = null; + } + } +} diff --git a/Vendor/sentry/lib/Raven/Compat.php b/Vendor/sentry/lib/Raven/Compat.php new file mode 100644 index 0000000..4d5f719 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Compat.php @@ -0,0 +1,180 @@ + $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 + * + * @param mixed $value + * @param int $options + * @param int $depth Set the maximum depth + * @return string + */ + public static function json_encode($value, $options = 0, $depth = 512) + { + if (function_exists('json_encode')) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + return json_encode($value); + } elseif (version_compare(PHP_VERSION, '5.5.0', '<')) { + return json_encode($value, $options); + } else { + return json_encode($value, $options, $depth); + } + } + + // @codeCoverageIgnoreStart + return self::_json_encode($value, $depth); + // @codeCoverageIgnoreEnd + } + + /** + * @param mixed $value + * @param int $depth Set the maximum depth + * @return string|false + */ + public static function _json_encode($value, $depth = 513) + { + if (ini_get('xdebug.extended_info') !== false) { + ini_set('xdebug.max_nesting_level', 2048); + } + return self::_json_encode_lowlevel($value, $depth); + } + + /** + * Implementation taken from + * http://www.mike-griffiths.co.uk/php-json_encode-alternative/ + * + * @param mixed $value + * @param int $depth Set the maximum depth + * @return string|false + */ + private static function _json_encode_lowlevel($value, $depth) + { + static $jsonReplaces = array( + array('\\', '/', "\n", "\t", "\r", "\f", '"'), + array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\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; + } + } elseif ($depth <= 1) { + return false; + } + + $isList = true; + for ($i = 0, reset($value); $i $v) { + $this_value = self::_json_encode($v, $depth - 1); + if ($this_value === false) { + return false; + } + $result[] = self::_json_encode($k, $depth - 1).':'.$this_value; + } + + return '{' . join(',', $result) . '}'; + } + } +} diff --git a/Vendor/sentry/lib/Raven/Context.php b/Vendor/sentry/lib/Raven/Context.php new file mode 100644 index 0000000..7487da4 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Context.php @@ -0,0 +1,36 @@ +clear(); + } + + /** + * Clean up existing context. + */ + public function clear() + { + $this->tags = array(); + $this->extra = array(); + $this->user = null; + } +} diff --git a/Vendor/sentry/lib/Raven/CurlHandler.php b/Vendor/sentry/lib/Raven/CurlHandler.php new file mode 100644 index 0000000..bcdd632 --- /dev/null +++ b/Vendor/sentry/lib/Raven/CurlHandler.php @@ -0,0 +1,119 @@ +options = $options; + $this->multi_handle = curl_multi_init(); + $this->requests = array(); + $this->join_timeout = $join_timeout; + + 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); + } + + /** + * @doc http://php.net/manual/en/function.curl-multi-exec.php + */ + protected 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/Vendor/sentry/lib/Raven/ErrorHandler.php b/Vendor/sentry/lib/Raven/ErrorHandler.php new file mode 100644 index 0000000..21358c1 --- /dev/null +++ b/Vendor/sentry/lib/Raven/ErrorHandler.php @@ -0,0 +1,195 @@ +registerExceptionHandler(); + * $error_handler->registerErrorHandler(); + * $error_handler->registerShutdownFunction(); + * + * @package raven + */ + +// TODO(dcramer): deprecate default error types in favor of runtime configuration +// unless a reason can be determined that making them dynamic is better. They +// currently are not used outside of the fatal handler. +class Raven_ErrorHandler +{ + protected $old_exception_handler; + protected $call_existing_exception_handler = false; + protected $old_error_handler; + protected $call_existing_error_handler = false; + protected $reservedMemory; + /** @var Raven_Client */ + protected $client; + protected $send_errors_last = false; + protected $fatal_error_types = array( + E_ERROR, + E_PARSE, + E_CORE_ERROR, + E_CORE_WARNING, + E_COMPILE_ERROR, + E_COMPILE_WARNING, + E_STRICT, + ); + + /** + * @var array + * Error types which should be processed by the handler. + * A 'null' value implies "whatever error_reporting is at time of error". + */ + protected $error_types = null; + + public function __construct($client, $send_errors_last = false, $error_types = null, + $__error_types = null) + { + // support legacy fourth argument for error types + if ($error_types === null) { + $error_types = $__error_types; + } + + $this->client = $client; + $this->error_types = $error_types; + $this->fatal_error_types = array_reduce($this->fatal_error_types, array($this, 'bitwiseOr')); + if ($send_errors_last) { + $this->send_errors_last = true; + $this->client->store_errors_for_bulk_send = true; + } + } + + public function bitwiseOr($a, $b) + { + return $a | $b; + } + + public function handleException($e, $isError = false, $vars = null) + { + $e->event_id = $this->client->captureException($e, null, null, $vars); + + if (!$isError && $this->call_existing_exception_handler) { + if ($this->old_exception_handler !== null) { + call_user_func($this->old_exception_handler, $e); + } else { + throw $e; + } + } + } + + public function handleError($type, $message, $file = '', $line = 0, $context = array()) + { + // http://php.net/set_error_handler + // The following error types cannot be handled with a user defined function: E_ERROR, + // E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and + // most of E_STRICT raised in the file where set_error_handler() is called. + + if (error_reporting() !== 0) { + $error_types = $this->error_types; + if ($error_types === null) { + $error_types = error_reporting(); + } + if ($error_types & $type) { + $e = new ErrorException($message, 0, $type, $file, $line); + $this->handleException($e, true, $context); + } + } + + if ($this->call_existing_error_handler) { + if ($this->old_error_handler !== null) { + return call_user_func( + $this->old_error_handler, + $type, + $message, + $file, + $line, + $context + ); + } else { + return false; + } + } + return true; + } + + public function handleFatalError() + { + unset($this->reservedMemory); + + if (null === $error = error_get_last()) { + return; + } + + if ($this->shouldCaptureFatalError($error['type'])) { + $e = new ErrorException( + @$error['message'], 0, @$error['type'], + @$error['file'], @$error['line'] + ); + $this->handleException($e, true); + } + } + + public function shouldCaptureFatalError($type) + { + return $type & $this->fatal_error_types; + } + + /** + * Register a handler which will intercept unhandled exceptions and report them to the + * associated Sentry client. + * + * @param bool $call_existing Call any existing exception handlers after processing + * this instance. + * @return Raven_ErrorHandler + */ + public function registerExceptionHandler($call_existing = true) + { + $this->old_exception_handler = set_exception_handler(array($this, 'handleException')); + $this->call_existing_exception_handler = $call_existing; + return $this; + } + + /** + * Register a handler which will intercept standard PHP errors and report them to the + * associated Sentry client. + * + * @param bool $call_existing Call any existing errors handlers after processing + * this instance. + * @param array $error_types All error types that should be sent. + * @return Raven_ErrorHandler + */ + public function registerErrorHandler($call_existing = true, $error_types = null) + { + if ($error_types !== null) { + $this->error_types = $error_types; + } + $this->old_error_handler = set_error_handler(array($this, 'handleError'), E_ALL); + $this->call_existing_error_handler = $call_existing; + return $this; + } + + /** + * Register a fatal error handler, which will attempt to capture errors which + * shutdown the PHP process. These are commonly things like OOM or timeouts. + * + * @param int $reservedMemorySize Number of kilobytes memory space to reserve, + * which is utilized when handling fatal errors. + * @return Raven_ErrorHandler + */ + public function registerShutdownFunction($reservedMemorySize = 10) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat('x', 1024 * $reservedMemorySize); + return $this; + } +} diff --git a/Vendor/sentry/lib/Raven/Exception.php b/Vendor/sentry/lib/Raven/Exception.php new file mode 100644 index 0000000..a6199f4 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Exception.php @@ -0,0 +1,4 @@ +client = $client; + } + + /** + * Override the default processor options + * + * @param array $options Associative array of processor options + */ + public function setProcessorOptions(array $options) + { + } + + /** + * 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/Vendor/sentry/lib/Raven/Processor/RemoveCookiesProcessor.php b/Vendor/sentry/lib/Raven/Processor/RemoveCookiesProcessor.php new file mode 100644 index 0000000..482f872 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Processor/RemoveCookiesProcessor.php @@ -0,0 +1,35 @@ + + */ +final class Raven_Processor_RemoveCookiesProcessor extends Raven_Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request'])) { + if (isset($data['request']['cookies'])) { + $data['request']['cookies'] = self::STRING_MASK; + } + + if (isset($data['request']['headers']) && isset($data['request']['headers']['Cookie'])) { + $data['request']['headers']['Cookie'] = self::STRING_MASK; + } + } + } +} diff --git a/Vendor/sentry/lib/Raven/Processor/RemoveHttpBodyProcessor.php b/Vendor/sentry/lib/Raven/Processor/RemoveHttpBodyProcessor.php new file mode 100644 index 0000000..3952ced --- /dev/null +++ b/Vendor/sentry/lib/Raven/Processor/RemoveHttpBodyProcessor.php @@ -0,0 +1,30 @@ + + */ +final class Raven_Processor_RemoveHttpBodyProcessor extends Raven_Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request'], $data['request']['method']) && in_array(strtoupper($data['request']['method']), array('POST', 'PUT', 'PATCH', 'DELETE'))) { + $data['request']['data'] = self::STRING_MASK; + } + } +} diff --git a/Vendor/sentry/lib/Raven/Processor/SanitizeDataProcessor.php b/Vendor/sentry/lib/Raven/Processor/SanitizeDataProcessor.php new file mode 100644 index 0000000..44cc77e --- /dev/null +++ b/Vendor/sentry/lib/Raven/Processor/SanitizeDataProcessor.php @@ -0,0 +1,160 @@ +fields_re = self::FIELDS_RE; + $this->values_re = self::VALUES_RE; + $this->session_cookie_name = ini_get('session.name'); + } + + /** + * {@inheritdoc} + */ + 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::STRING_MASK; + } + + if (empty($key)) { + return; + } + + if (preg_match($this->fields_re, $key)) { + $item = self::STRING_MASK; + } + } + + public function sanitizeException(&$data) + { + foreach ($data['exception']['values'] as &$value) { + $this->sanitizeStacktrace($value['stacktrace']); + } + } + + public function sanitizeHttp(&$data) + { + $http = &$data['request']; + if (!empty($http['cookies']) && is_array($http['cookies'])) { + $cookies = &$http['cookies']; + if (!empty($cookies[$this->session_cookie_name])) { + $cookies[$this->session_cookie_name] = self::STRING_MASK; + } + } + if (!empty($http['data']) && is_array($http['data'])) { + array_walk_recursive($http['data'], array($this, 'sanitize')); + } + } + + public function sanitizeStacktrace(&$data) + { + foreach ($data['frames'] as &$frame) { + if (empty($frame['vars'])) { + continue; + } + array_walk_recursive($frame['vars'], array($this, 'sanitize')); + } + } + + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (!empty($data['exception'])) { + $this->sanitizeException($data); + } + if (!empty($data['stacktrace'])) { + $this->sanitizeStacktrace($data['stacktrace']); + } + if (!empty($data['request'])) { + $this->sanitizeHttp($data); + } + if (!empty($data['extra'])) { + array_walk_recursive($data['extra'], 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/Vendor/sentry/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php b/Vendor/sentry/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php new file mode 100644 index 0000000..c058c1f --- /dev/null +++ b/Vendor/sentry/lib/Raven/Processor/SanitizeHttpHeadersProcessor.php @@ -0,0 +1,64 @@ + + */ +final class Raven_Processor_SanitizeHttpHeadersProcessor extends Raven_Processor +{ + /** + * @var string[] $httpHeadersToSanitize The list of HTTP headers to sanitize + */ + private $httpHeadersToSanitize = array(); + + /** + * {@inheritdoc} + */ + public function __construct(Raven_Client $client) + { + parent::__construct($client); + } + + /** + * {@inheritdoc} + */ + public function setProcessorOptions(array $options) + { + $this->httpHeadersToSanitize = array_merge($this->getDefaultHeaders(), isset($options['sanitize_http_headers']) ? $options['sanitize_http_headers'] : array()); + } + + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (isset($data['request']) && isset($data['request']['headers'])) { + foreach ($data['request']['headers'] as $header => &$value) { + if (in_array($header, $this->httpHeadersToSanitize)) { + $value = self::STRING_MASK; + } + } + } + } + + /** + * Gets the list of default headers that must be sanitized. + * + * @return string[] + */ + private function getDefaultHeaders() + { + return array('Authorization', 'Proxy-Authorization', 'X-Csrf-Token', 'X-CSRFToken', 'X-XSRF-TOKEN'); + } +} diff --git a/Vendor/sentry/lib/Raven/Processor/SanitizeStacktraceProcessor.php b/Vendor/sentry/lib/Raven/Processor/SanitizeStacktraceProcessor.php new file mode 100644 index 0000000..96e1526 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Processor/SanitizeStacktraceProcessor.php @@ -0,0 +1,39 @@ + + */ +class Raven_Processor_SanitizeStacktraceProcessor extends Raven_Processor +{ + /** + * {@inheritdoc} + */ + public function process(&$data) + { + if (!isset($data['exception'], $data['exception']['values'])) { + return; + } + + foreach ($data['exception']['values'] as &$exception) { + if (!isset($exception['stacktrace'])) { + continue; + } + + foreach ($exception['stacktrace']['frames'] as &$frame) { + unset($frame['pre_context'], $frame['context_line'], $frame['post_context']); + } + } + } +} diff --git a/Vendor/sentry/lib/Raven/ReprSerializer.php b/Vendor/sentry/lib/Raven/ReprSerializer.php new file mode 100644 index 0000000..dc70469 --- /dev/null +++ b/Vendor/sentry/lib/Raven/ReprSerializer.php @@ -0,0 +1,41 @@ +serializeString($value); + } + } +} diff --git a/Vendor/sentry/lib/Raven/SanitizeDataProcessor.php b/Vendor/sentry/lib/Raven/SanitizeDataProcessor.php new file mode 100644 index 0000000..06e7a93 --- /dev/null +++ b/Vendor/sentry/lib/Raven/SanitizeDataProcessor.php @@ -0,0 +1,19 @@ +mb_detect_order = $mb_detect_order; + } + } + + /** + * Serialize an object (recursively) into something safe for data + * sanitization and encoding. + * + * @param mixed $value + * @param int $max_depth + * @param int $_depth + * @return string|bool|double|int|null|object|array + */ + public function serialize($value, $max_depth = 3, $_depth = 0) + { + $className = is_object($value) ? get_class($value) : null; + $toArray = is_array($value) || $className === 'stdClass'; + if ($toArray && $_depth < $max_depth) { + $new = array(); + foreach ($value as $k => $v) { + $new[$this->serializeValue($k)] = $this->serialize($v, $max_depth, $_depth + 1); + } + + return $new; + } + return $this->serializeValue($value); + } + + protected function serializeString($value) + { + $value = (string) $value; + if (function_exists('mb_detect_encoding') + && function_exists('mb_convert_encoding') + ) { + // we always guarantee this is coerced, even if we can't detect encoding + if ($currentEncoding = mb_detect_encoding($value, $this->mb_detect_order)) { + $value = mb_convert_encoding($value, 'UTF-8', $currentEncoding); + } else { + $value = mb_convert_encoding($value, 'UTF-8'); + } + } + + if (strlen($value) > 1024) { + $value = substr($value, 0, 1014) . ' {clipped}'; + } + + return $value; + } + + /** + * @param mixed $value + * @return string|bool|double|int|null + */ + protected function serializeValue($value) + { + if (is_null($value) || is_bool($value) || is_float($value) || is_integer($value)) { + return $value; + } 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); + } else { + return $this->serializeString($value); + } + } + + + /** + * @return string + * @codeCoverageIgnore + */ + public function getMbDetectOrder() + { + return $this->mb_detect_order; + } + + /** + * @param string $mb_detect_order + * + * @return Raven_Serializer + * @codeCoverageIgnore + */ + public function setMbDetectOrder($mb_detect_order) + { + $this->mb_detect_order = $mb_detect_order; + + return $this; + } +} diff --git a/Vendor/sentry/lib/Raven/Stacktrace.php b/Vendor/sentry/lib/Raven/Stacktrace.php new file mode 100644 index 0000000..6430789 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Stacktrace.php @@ -0,0 +1,305 @@ +getFileName(); + } catch (ReflectionException $e) { + // Forget it if we run into errors, it's not worth it. + } + } else { + $context['line'] = sprintf('%s(anonymous)', $frame['function']); + } + + if (empty($context['filename'])) { + $context['filename'] = $filename = '[Anonymous function]'; + } + + $abs_path = ''; + $context['prefix'] = ''; + $context['suffix'] = ''; + $context['lineno'] = 0; + } else { + $context = self::read_source_file($frame['file'], $frame['line']); + $abs_path = $frame['file']; + } + + // strip base path if present + $context['filename'] = self::strip_prefixes($context['filename'], $strip_prefixes); + if ($i === 0 && isset($errcontext)) { + // If we've been given an error context that can be used as the vars for the first frame. + $vars = $errcontext; + } else { + if ($trace) { + $vars = self::get_frame_context($nextframe, $frame_var_limit); + } else { + $vars = array(); + } + } + + $data = array( + 'filename' => $context['filename'], + 'lineno' => (int) $context['lineno'], + 'function' => isset($nextframe['function']) ? $nextframe['function'] : null, + 'pre_context' => $serializer->serialize($context['prefix']), + 'context_line' => $serializer->serialize($context['line']), + 'post_context' => $serializer->serialize($context['suffix']), + ); + + // detect in_app based on app path + if ($app_path) { + $norm_abs_path = @realpath($abs_path) ?: $abs_path; + if (!$abs_path) { + $in_app = false; + } else { + $in_app = (bool)(substr($norm_abs_path, 0, strlen($app_path)) === $app_path); + } + if ($in_app && $excluded_app_paths) { + foreach ($excluded_app_paths as $path) { + if (substr($norm_abs_path, 0, strlen($path)) === $path) { + $in_app = false; + break; + } + } + } + $data['in_app'] = $in_app; + } + + // 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)) { + $cleanVars = array(); + foreach ($vars as $key => $value) { + $value = $reprSerializer->serialize($value); + if (is_string($value) || is_numeric($value)) { + $cleanVars[(string)$key] = substr($value, 0, $frame_var_limit); + } else { + $cleanVars[(string)$key] = $value; + } + } + $data['vars'] = $cleanVars; + } + + $result[] = $data; + } + + return array_reverse($result); + } + + public static function get_default_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) + { + if (!isset($frame['args'])) { + return array(); + } + + $i = 1; + $args = array(); + foreach ($frame['args'] as $arg) { + $args['param'.$i] = self::serialize_argument($arg, $frame_arg_limit); + $i++; + } + return $args; + } + + public static function get_frame_context($frame, $frame_arg_limit = Raven_Client::MESSAGE_LIMIT) + { + if (!isset($frame['args'])) { + return array(); + } + + // 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 self::get_default_context($frame, $frame_arg_limit); + } + if (strpos($frame['function'], '__lambda_func') !== false) { + return self::get_default_context($frame, $frame_arg_limit); + } + if (isset($frame['class']) && $frame['class'] == 'Closure') { + return self::get_default_context($frame, $frame_arg_limit); + } + if (strpos($frame['function'], '{closure}') !== false) { + return self::get_default_context($frame, $frame_arg_limit); + } + if (in_array($frame['function'], self::$statements)) { + if (empty($frame['args'])) { + // No arguments + return array(); + } else { + // Sanitize the file path + return array( + 'param1' => self::serialize_argument($frame['args'][0], $frame_arg_limit), + ); + } + } + 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'); + } + } elseif (function_exists($frame['function'])) { + $reflection = new ReflectionFunction($frame['function']); + } else { + return self::get_default_context($frame, $frame_arg_limit); + } + } catch (ReflectionException $e) { + return self::get_default_context($frame, $frame_arg_limit); + } + + $params = $reflection->getParameters(); + + $args = array(); + foreach ($frame['args'] as $i => $arg) { + $arg = self::serialize_argument($arg, $frame_arg_limit); + if (isset($params[$i])) { + // Assign the argument by the parameter name + $args[$params[$i]->name] = $arg; + } else { + $args['param'.$i] = $arg; + } + } + + return $args; + } + + private static function serialize_argument($arg, $frame_arg_limit) + { + if (is_array($arg)) { + $_arg = array(); + foreach ($arg as $key => $value) { + if (is_string($value) || is_numeric($value)) { + $_arg[$key] = substr($value, 0, $frame_arg_limit); + } else { + $_arg[$key] = $value; + } + } + return $_arg; + } elseif (is_string($arg) || is_numeric($arg)) { + return substr($arg, 0, $frame_arg_limit); + } else { + return $arg; + } + } + + private static function strip_prefixes($filename, $prefixes) + { + if ($prefixes === null) { + return $filename; + } + foreach ($prefixes as $prefix) { + if (substr($filename, 0, strlen($prefix)) === $prefix) { + return substr($filename, strlen($prefix)); + } + } + return $filename; + } + + 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/Vendor/sentry/lib/Raven/TransactionStack.php b/Vendor/sentry/lib/Raven/TransactionStack.php new file mode 100644 index 0000000..9446809 --- /dev/null +++ b/Vendor/sentry/lib/Raven/TransactionStack.php @@ -0,0 +1,54 @@ +stack = array(); + } + + public function clear() + { + $this->stack = array(); + } + + public function peek() + { + $len = count($this->stack); + if ($len === 0) { + return null; + } + return $this->stack[$len - 1]; + } + + public function push($context) + { + $this->stack[] = $context; + } + + /** @noinspection PhpInconsistentReturnPointsInspection + * @param string|null $context + * @return mixed + */ + public function pop($context = null) + { + if (!$context) { + return array_pop($this->stack); + } + while (!empty($this->stack)) { + if (array_pop($this->stack) === $context) { + return $context; + } + } + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd +} diff --git a/Vendor/sentry/lib/Raven/Util.php b/Vendor/sentry/lib/Raven/Util.php new file mode 100644 index 0000000..8f684b5 --- /dev/null +++ b/Vendor/sentry/lib/Raven/Util.php @@ -0,0 +1,38 @@ + + + + + + ./test/Raven/ + + + + + + ./lib/Raven/ + + + diff --git a/Vendor/sentry/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php b/Vendor/sentry/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php new file mode 100644 index 0000000..20396ab --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/Breadcrumbs/ErrorHandlerTest.php @@ -0,0 +1,29 @@ + false, + )); + + $handler = new \Raven_Breadcrumbs_ErrorHandler($client); + $handler->handleError(E_WARNING, 'message'); + + $crumbs = $client->breadcrumbs->fetch(); + $this->assertEquals(count($crumbs), 1); + $this->assertEquals($crumbs[0]['message'], 'message'); + $this->assertEquals($crumbs[0]['category'], 'error_reporting'); + $this->assertEquals($crumbs[0]['level'], 'warning'); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/Breadcrumbs/MonologTest.php b/Vendor/sentry/test/Raven/Tests/Breadcrumbs/MonologTest.php new file mode 100644 index 0000000..e4a4a38 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/Breadcrumbs/MonologTest.php @@ -0,0 +1,72 @@ +run(Object(Illuminate\Http\Request)) +#3 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(5053): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request)) +#4 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(715): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request)) +#5 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(696): Illuminate\Foundation\Application->dispatch(Object(Illuminate\Http\Request)) +#6 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(7825): Illuminate\Foundation\Application->handle(Object(Illuminate\Http\Request), 1, true) +#7 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8432): Illuminate\Session\Middleware->handle(Object(Illuminate\Http\Request), 1, true) +#8 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(8379): Illuminate\Cookie\Queue->handle(Object(Illuminate\Http\Request), 1, true) +#9 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(11123): Illuminate\Cookie\Guard->handle(Object(Illuminate\Http\Request), 1, true) +#10 /sentry-laravel/examples/laravel-4.2/bootstrap/compiled.php(657): Stack\StackedHttpKernel->handle(Object(Illuminate\Http\Request)) +#11 /sentry-laravel/examples/laravel-4.2/public/index.php(49): Illuminate\Foundation\Application->run() +#12 /sentry-laravel/examples/laravel-4.2/server.php(19): require_once('/Users/dcramer/...') +#13 {main} +EOF; + } + + public function testSimple() + { + $client = new \Raven_Client(array( + 'install_default_breadcrumb_handlers' => false, + )); + $handler = new \Raven_Breadcrumbs_MonologHandler($client); + + $logger = new Monolog\Logger('sentry'); + $logger->pushHandler($handler); + $logger->addWarning('Foo'); + + $crumbs = $client->breadcrumbs->fetch(); + $this->assertEquals(count($crumbs), 1); + $this->assertEquals($crumbs[0]['message'], 'Foo'); + $this->assertEquals($crumbs[0]['category'], 'sentry'); + $this->assertEquals($crumbs[0]['level'], 'warning'); + } + + public function testErrorInMessage() + { + $client = new \Raven_Client(array( + 'install_default_breadcrumb_handlers' => false, + )); + $handler = new \Raven_Breadcrumbs_MonologHandler($client); + + $logger = new Monolog\Logger('sentry'); + $logger->pushHandler($handler); + $logger->addError($this->getSampleErrorMessage()); + + $crumbs = $client->breadcrumbs->fetch(); + $this->assertEquals(count($crumbs), 1); + $this->assertEquals($crumbs[0]['data']['type'], 'Exception'); + $this->assertEquals($crumbs[0]['data']['value'], 'An unhandled exception'); + $this->assertEquals($crumbs[0]['category'], 'sentry'); + $this->assertEquals($crumbs[0]['level'], 'error'); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/BreadcrumbsTest.php b/Vendor/sentry/test/Raven/Tests/BreadcrumbsTest.php new file mode 100644 index 0000000..eb4f026 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/BreadcrumbsTest.php @@ -0,0 +1,38 @@ +record(array('message' => $i)); + } + + $results = $breadcrumbs->fetch(); + + $this->assertEquals(count($results), 10); + for ($i = 1; $i <= 10; $i++) { + $this->assertEquals($results[$i - 1]['message'], $i); + } + } + + public function testJson() + { + $breadcrumbs = new Raven_Breadcrumbs(1); + $breadcrumbs->record(array('message' => 'test')); + $json = $breadcrumbs->to_json(); + + $this->assertEquals(count($json['values']), 1); + $this->assertEquals($json['values'][0]['message'], 'test'); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/ClientTest.php b/Vendor/sentry/test/Raven/Tests/ClientTest.php new file mode 100644 index 0000000..6982f54 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/ClientTest.php @@ -0,0 +1,2247 @@ +__sent_events; + } + + public function send(&$data) + { + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { + // if send_callback returns falsely, end native send + return; + } + $this->__sent_events[] = $data; + } + + public static function is_http_request() + { + return true; + } + + public static 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(); + } + + public function buildCurlCommand($url, $data, $headers) + { + return parent::buildCurlCommand($url, $data, $headers); + } + + // short circuit breadcrumbs + public function registerDefaultBreadcrumbHandlers() + { + $this->dummy_breadcrumbs_handlers_has_set = true; + } + + public function registerShutdownFunction() + { + $this->dummy_shutdown_handlers_has_set = true; + } + + /** + * Expose the current url method to test it + * + * @return string + */ + public function test_get_current_url() + { + return $this->get_current_url(); + } +} + +class Dummy_Raven_Client_With_Overrided_Direct_Send extends Raven_Client +{ + public $_send_http_asynchronous_curl_exec_called = false; + public $_send_http_synchronous = false; + public $_set_url; + public $_set_data; + public $_set_headers; + public static $_close_curl_resource_called = false; + + public function send_http_asynchronous_curl_exec($url, $data, $headers) + { + $this->_send_http_asynchronous_curl_exec_called = true; + $this->_set_url = $url; + $this->_set_data = $data; + $this->_set_headers = $headers; + } + + public function send_http_synchronous($url, $data, $headers) + { + $this->_send_http_synchronous = true; + $this->_set_url = $url; + $this->_set_data = $data; + $this->_set_headers = $headers; + } + + public function get_curl_options() + { + $options = parent::get_curl_options(); + + return $options; + } + + public function get_curl_handler() + { + return $this->_curl_handler; + } + + public function set_curl_handler(Raven_CurlHandler $value) + { + $this->_curl_handler = $value; + } + + public function close_curl_resource() + { + parent::close_curl_resource(); + self::$_close_curl_resource_called = true; + } +} + +class Dummy_Raven_Client_No_Http extends Dummy_Raven_Client +{ + /** + * @return bool + */ + public static function is_http_request() + { + return false; + } +} + +class Dummy_Raven_Client_With_Sync_Override extends Raven_Client +{ + private static $_test_data = null; + + public static function get_test_data() + { + if (is_null(self::$_test_data)) { + self::$_test_data = ''; + for ($i = 0; $i < 128; $i++) { + self::$_test_data .= chr(mt_rand(ord('a'), ord('z'))); + } + } + + return self::$_test_data; + } + + public static function test_filename() + { + return sys_get_temp_dir().'/clientraven.tmp'; + } + + protected function buildCurlCommand($url, $data, $headers) + { + return 'echo '.escapeshellarg(self::get_test_data()).' > '.self::test_filename(); + } +} + +class Dummy_Raven_CurlHandler extends Raven_CurlHandler +{ + public $_set_url; + public $_set_data; + public $_set_headers; + public $_enqueue_called = false; + public $_join_called = false; + + public function __construct($options = array(), $join_timeout = 5) + { + parent::__construct($options, $join_timeout); + } + + public function enqueue($url, $data = null, $headers = array()) + { + $this->_enqueue_called = true; + $this->_set_url = $url; + $this->_set_data = $data; + $this->_set_headers = $headers; + + return 0; + } + + public function join($timeout = null) + { + $this->_join_called = true; + } +} + +class Raven_Tests_ClientTest extends \PHPUnit\Framework\TestCase +{ + public function tearDown() + { + parent::tearDown(); + if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { + unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); + } + } + + 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(1, $result['project']); + $this->assertEquals('http://example.com/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); + } + + public function testParseDSNHttps() + { + $result = Raven_Client::ParseDSN('https://public:secret@example.com/1'); + + $this->assertEquals(1, $result['project']); + $this->assertEquals('https://example.com/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); + } + + public function testParseDSNPath() + { + $result = Raven_Client::ParseDSN('http://public:secret@example.com/app/1'); + + $this->assertEquals(1, $result['project']); + $this->assertEquals('http://example.com/app/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); + } + + public function testParseDSNPort() + { + $result = Raven_Client::ParseDSN('http://public:secret@example.com:9000/app/1'); + + $this->assertEquals(1, $result['project']); + $this->assertEquals('http://example.com:9000/app/api/1/store/', $result['server']); + $this->assertEquals('public', $result['public_key']); + $this->assertEquals('secret', $result['secret_key']); + } + + 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'); + } + + /** + * @covers Raven_Client::__construct + */ + public function testDsnFirstArgument() + { + $client = new Dummy_Raven_Client('http://public:secret@example.com/1'); + + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); + } + + /** + * @covers Raven_Client::__construct + */ + public function testDsnFirstArgumentWithOptions() + { + $client = new Dummy_Raven_Client('http://public:secret@example.com/1', array( + 'site' => 'foo', + )); + + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); + $this->assertEquals('foo', $client->site); + } + + /** + * @covers Raven_Client::__construct + */ + public function testOptionsFirstArgument() + { + $client = new Dummy_Raven_Client(array( + 'server' => 'http://example.com/api/1/store/', + 'project' => 1, + )); + + $this->assertEquals('http://example.com/api/1/store/', $client->server); + } + + /** + * @covers Raven_Client::__construct + */ + public function testDsnInOptionsFirstArg() + { + $client = new Dummy_Raven_Client(array( + 'dsn' => 'http://public:secret@example.com/1', + )); + + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); + } + + /** + * @covers Raven_Client::__construct + */ + public function testDsnInOptionsSecondArg() + { + $client = new Dummy_Raven_Client(null, array( + 'dsn' => 'http://public:secret@example.com/1', + )); + + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); + } + + /** + * @covers Raven_Client::__construct + */ + public function testOptionsFirstArgumentWithOptions() + { + $client = new Dummy_Raven_Client(array( + 'server' => 'http://example.com/api/1/store/', + 'project' => 1, + ), array( + 'site' => 'foo', + )); + + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('foo', $client->site); + } + + /** + * @covers Raven_Client::captureMessage + */ + 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(1, count($events)); + $event = array_pop($events); + $this->assertEquals('bar', $event['extra']['foo']); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testOptionsExtraDataWithNull() + { + $client = new Dummy_Raven_Client(array('extra' => array('foo' => 'bar'))); + + $client->captureMessage('Test Message %s', array('foo'), null); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals('bar', $event['extra']['foo']); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testEmptyExtraData() + { + $client = new Dummy_Raven_Client(array('extra' => array())); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals(array_key_exists('extra', $event), false); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testCaptureMessageDoesHandleUninterpolatedMessage() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s'); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals('Test Message %s', $event['message']); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testCaptureMessageDoesHandleInterpolatedMessage() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals('Test Message foo', $event['message']); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testCaptureMessageDoesHandleInterpolatedMessageWithRelease() + { + $client = new Dummy_Raven_Client(); + $client->setRelease(20160909144742); + + $this->assertEquals(20160909144742, $client->getRelease()); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals(20160909144742, $event['release']); + $this->assertEquals('Test Message foo', $event['message']); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testCaptureMessageSetsInterface() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s', array('foo')); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals(array( + 'message' => 'Test Message %s', + 'params' => array('foo'), + 'formatted' => 'Test Message foo', + ), $event['sentry.interfaces.Message']); + $this->assertEquals('Test Message foo', $event['message']); + } + + /** + * @covers Raven_Client::captureMessage + */ + 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(1, count($events)); + $event = array_pop($events); + $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); + $this->assertEquals('bar', $event['extra']['foo']); + $this->assertEquals('Test Message foo', $event['message']); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testCaptureMessageHandlesLevelAsThirdArg() + { + $client = new Dummy_Raven_Client(); + + $client->captureMessage('Test Message %s', array('foo'), Dummy_Raven_Client::WARNING); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); + $this->assertEquals('Test Message foo', $event['message']); + } + + /** + * @covers Raven_Client::captureException + */ + 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(1, count($events)); + $event = array_pop($events); + + $exc = $event['exception']; + $this->assertEquals(1, count($exc['values'])); + $this->assertEquals('Foo bar', $exc['values'][0]['value']); + $this->assertEquals('Exception', $exc['values'][0]['type']); + + $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('create_exception', $frame['function']); + $this->assertFalse(isset($frame['vars'])); + $this->assertEquals(' throw new Exception(\'Foo bar\');', $frame['context_line']); + $this->assertFalse(empty($frame['pre_context'])); + $this->assertFalse(empty($frame['post_context'])); + } + + /** + * @covers Raven_Client::captureException + */ + 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(1, count($events)); + $event = array_pop($events); + + $exc = $event['exception']; + $this->assertEquals(2, count($exc['values'])); + $this->assertEquals('Foo bar', $exc['values'][0]['value']); + $this->assertEquals('Child exc', $exc['values'][1]['value']); + } + + /** + * @covers Raven_Client::captureException + */ + 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(Dummy_Raven_Client::ERROR, $event['level']); + + $event = array_pop($events); + $this->assertEquals(Dummy_Raven_Client::INFO, $event['level']); + + $event = array_pop($events); + $this->assertEquals(Dummy_Raven_Client::WARNING, $event['level']); + } + + /** + * @covers Raven_Client::captureException + */ + public function testCaptureExceptionHandlesOptionsAsSecondArg() + { + $client = new Dummy_Raven_Client(); + $ex = $this->create_exception(); + $client->captureException($ex, array('culprit' => 'test')); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals('test', $event['culprit']); + } + + /** + * @covers Raven_Client::captureException + */ + 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(0, count($events)); + } + + public function testCaptureExceptionInvalidUTF8() + { + $client = new Dummy_Raven_Client(); + try { + invalid_encoding(); + } catch (Exception $ex) { + $client->captureException($ex); + } + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + + // if this fails to encode it returns false + $message = $client->encode($events[0]); + $this->assertNotFalse($message, $client->getLastError()); + } + + /** + * @covers Raven_Client::__construct + */ + public function testDoesRegisterProcessors() + { + $client = new Dummy_Raven_Client(array( + 'processors' => array('Raven_Processor_SanitizeDataProcessor'), + )); + + $this->assertEquals(1, count($client->processors)); + $this->assertInstanceOf('Raven_Processor_SanitizeDataProcessor', $client->processors[0]); + } + + public function testProcessDoesCallProcessors() + { + $data = array("key"=>"value"); + + $processor = $this->getMockBuilder('Processor') + ->setMethods(array('process')) + ->getMock(); + $processor->expects($this->once()) + ->method('process') + ->with($data); + + $client = new Dummy_Raven_Client(); + $client->processors[] = $processor; + $client->process($data); + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::getDefaultProcessors + */ + public function testDefaultProcessorsAreUsed() + { + $client = new Dummy_Raven_Client(); + $defaults = Dummy_Raven_Client::getDefaultProcessors(); + + $this->assertEquals(count($defaults), count($client->processors)); + } + + /** + * @covers Raven_Client::getDefaultProcessors + */ + public function testDefaultProcessorsContainSanitizeDataProcessor() + { + $this->assertContains('Raven_Processor_SanitizeDataProcessor', Dummy_Raven_Client::getDefaultProcessors()); + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::get_default_data + */ + public function testGetDefaultData() + { + $client = new Dummy_Raven_Client(); + $client->transaction->push('test'); + $expected = array( + 'platform' => 'php', + 'project' => $client->project, + 'server_name' => $client->name, + 'site' => $client->site, + 'logger' => $client->logger, + 'tags' => $client->tags, + 'sdk' => array( + 'name' => 'sentry-php', + 'version' => $client::VERSION, + ), + 'culprit' => 'test', + ); + $this->assertEquals($expected, $client->get_default_data()); + } + + /** + * @backupGlobals + * @covers Raven_Client::get_http_data + */ + 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', + '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( + 'request' => 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', + ), + ) + ); + + $client = new Dummy_Raven_Client(); + $this->assertEquals($expected, $client->get_http_data()); + } + + /** + * @covers Raven_Client::user_context + * @covers Raven_Client::get_user_data + */ + public function testGetUserDataWithSetUser() + { + $client = new Dummy_Raven_Client(); + + $id = 'unique_id'; + $email = 'foo@example.com'; + $client->user_context(array('id' => $id, 'email' => $email, 'username' => 'my_user', )); + + $expected = array( + 'user' => array( + 'id' => $id, + 'username' => 'my_user', + 'email' => $email, + ) + ); + + $this->assertEquals($expected, $client->get_user_data()); + } + + /** + * @covers Raven_Client::get_user_data + */ + public function testGetUserDataWithNoUser() + { + $client = new Dummy_Raven_Client(); + + $expected = array( + 'user' => array( + 'id' => session_id(), + ) + ); + $this->assertEquals($expected, $client->get_user_data()); + } + + /** + * @covers Raven_Client::get_auth_header + */ + public function testGet_Auth_Header() + { + $client = new Dummy_Raven_Client(); + + $clientstring = 'sentry-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, 'sentry-php/test', 'publickey', 'secretkey')); + } + + /** + * @covers Raven_Client::getAuthHeader + */ + public function testGetAuthHeader() + { + $client = new Dummy_Raven_Client(); + $ts1 = microtime(true); + $header = $client->getAuthHeader(); + $ts2 = microtime(true); + $this->assertEquals(1, preg_match('/sentry_timestamp=([0-9.]+)/', $header, $a)); + $this->assertRegExp('/^[0-9]+(\\.[0-9]+)?$/', $a[1]); + $this->assertGreaterThanOrEqual($ts1, (double)$a[1]); + $this->assertLessThanOrEqual($ts2, (double)$a[1]); + } + + /** + * @covers Raven_Client::captureMessage + */ + 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['user']); + } + + /** + * @covers Raven_Client::captureMessage + */ + public function testCaptureMessageWithUnserializableUserData() + { + $client = new Dummy_Raven_Client(); + + $client->user_context(array( + 'email' => 'foo@example.com', + 'data' => array( + 'error' => new Exception('test'), + ) + )); + + $client->captureMessage('test'); + $events = $client->getSentEvents(); + // we're just asserting that this goes off without a hitch + $this->assertEquals(1, count($events)); + array_pop($events); + } + + /** + * @covers Raven_Client::captureMessage + * @covers Raven_Client::tags_context + */ + 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']); + } + + /** + * @covers Raven_Client::captureMessage + * @covers Raven_Client::extra_context + */ + 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']); + } + + /** + * @covers Raven_Client::captureException + */ + public function testCaptureExceptionContainingLatin1() + { + // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order + $options = array( + 'mb_detect_order' => array( + 'ISO-8859-1', 'ASCII', 'UTF-8' + ) + ); + + $client = new Dummy_Raven_Client($options); + + // we need a non-utf8 string here. + // nobody writes non-utf8 in exceptions, but it is the easiest way to test. + // in real live non-utf8 may be somewhere in the exception's stacktrace + $utf8String = 'äöü'; + $latin1String = utf8_decode($utf8String); + $client->captureException(new \Exception($latin1String)); + + $events = $client->getSentEvents(); + $event = array_pop($events); + + $this->assertEquals($utf8String, $event['exception']['values'][0]['value']); + } + + + public function testCaptureExceptionInLatin1File() + { + // If somebody has a non-utf8 codebase, she/he should add the encoding to the detection order + $options = array( + 'mb_detect_order' => array( + 'ISO-8859-1', 'ASCII', 'UTF-8' + ) + ); + + $client = new Dummy_Raven_Client($options); + + require_once(__DIR__.'/resources/captureExceptionInLatin1File.php'); + + $events = $client->getSentEvents(); + $event = array_pop($events); + + $stackTrace = array_pop($event['exception']['values'][0]['stacktrace']['frames']); + + $utf8String = "// äöü"; + $found = false; + foreach ($stackTrace['pre_context'] as $line) { + if ($line == $utf8String) { + $found = true; + break; + } + } + + $this->assertTrue($found); + } + + /** + * @covers Raven_Client::captureLastError + */ + public function testCaptureLastError() + { + if (function_exists('error_clear_last')) { + error_clear_last(); + } + $client = new Dummy_Raven_Client(); + $this->assertNull($client->captureLastError()); + $this->assertEquals(0, count($client->getSentEvents())); + + @$undefined; + + $client->captureLastError(); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals('Undefined variable: undefined', $event['exception']['values'][0]['value']); + } + + /** + * @covers Raven_Client::getLastEventID + */ + public function testGetLastEventID() + { + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => 'test', 'event_id' => 'abc')); + $this->assertEquals('abc', $client->getLastEventID()); + } + + /** + * @covers Raven_Client::setTransport + */ + public function testCustomTransport() + { + $events = array(); + + // transport test requires default client + $client = new Raven_Client('https://public:secret@sentry.example.com/1', array( + 'install_default_breadcrumb_handlers' => false, + )); + $client->setTransport(function ($client, $data) use (&$events) { + $events[] = $data; + }); + $client->capture(array('message' => 'test', 'event_id' => 'abc')); + $this->assertEquals(1, count($events)); + } + + /** + * @covers Raven_Client::setAppPath + */ + public function testAppPathLinux() + { + $client = new Dummy_Raven_Client(); + $client->setAppPath('/foo/bar'); + + $this->assertEquals('/foo/bar/', $client->getAppPath()); + + $client->setAppPath('/foo/baz/'); + + $this->assertEquals('/foo/baz/', $client->getAppPath()); + } + + /** + * @covers Raven_Client::setAppPath + */ + public function testAppPathWindows() + { + $client = new Dummy_Raven_Client(); + $client->setAppPath('C:\\foo\\bar\\'); + + $this->assertEquals('C:\\foo\\bar\\', $client->getAppPath()); + } + + /** + * @expectedException Raven_Exception + * @expectedExceptionMessage Raven_Client->install() must only be called once + */ + public function testCannotInstallTwice() + { + $client = new Dummy_Raven_Client('https://public:secret@sentry.example.com/1'); + $client->install(); + $client->install(); + } + + public function cb1($data) + { + $this->assertEquals('test', $data['message']); + return false; + } + + public function cb2($data) + { + $this->assertEquals('test', $data['message']); + return true; + } + + public function cb3(&$data) + { + unset($data['message']); + return true; + } + + /** + * @covers Raven_Client::send + */ + 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)); + + $client = new Dummy_Raven_Client(array('send_callback' => array($this, 'cb3'))); + $client->captureMessage('test'); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $this->assertTrue(empty($events[0]['message'])); + } + + /** + * @covers Raven_Client::sanitize + */ + public function testSanitizeExtra() + { + $client = new Dummy_Raven_Client(); + $data = array('extra' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array(2), 3 + ), + ), + )); + $client->sanitize($data); + + $this->assertEquals(array('extra' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, 'Array of length 1', 3 + ), + ), + )), $data); + } + + /** + * @covers Raven_Client::sanitize + */ + public function testSanitizeTags() + { + $client = new Dummy_Raven_Client(); + $data = array('tags' => array( + 'foo' => 'bar', + 'baz' => array('biz'), + )); + $client->sanitize($data); + + $this->assertEquals(array('tags' => array( + 'foo' => 'bar', + 'baz' => 'Array', + )), $data); + } + + /** + * @covers Raven_Client::sanitize + */ + public function testSanitizeUser() + { + $client = new Dummy_Raven_Client(); + $data = array('user' => array( + 'email' => 'foo@example.com', + )); + $client->sanitize($data); + + $this->assertEquals(array('user' => array( + 'email' => 'foo@example.com', + )), $data); + } + + /** + * @covers Raven_Client::sanitize + */ + public function testSanitizeRequest() + { + $client = new Dummy_Raven_Client(); + $data = array('request' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array(2), 3 + ), + ), + )); + $client->sanitize($data); + + $this->assertEquals(array('request' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, 'Array of length 1', 3 + ), + ), + )), $data); + } + + /** + * @covers Raven_Client::sanitize + */ + public function testSanitizeContexts() + { + $client = new Dummy_Raven_Client(); + $data = array('contexts' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array( + 'foo' => 'bar', + 'level4' => array(array('level5', 'level5 a'), 2), + ), 3 + ), + ), + )); + $client->sanitize($data); + + $this->assertEquals(array('contexts' => array( + 'context' => array( + 'line' => 1216, + 'stack' => array( + 1, array( + 'foo' => 'bar', + 'level4' => array('Array of length 2', 2), + ), 3 + ), + ), + )), $data); + } + + /** + * @covers Raven_Client::buildCurlCommand + */ + public function testBuildCurlCommandEscapesInput() + { + $data = '{"foo": "\'; ls;"}'; + $client = new Dummy_Raven_Client(); + $result = $client->buildCurlCommand('http://foo.com', $data, array()); + $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); + + $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); + $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 > /dev/null 2>&1 &', $result); + + $client->verify_ssl = false; + $result = $client->buildCurlCommand('http://foo.com', $data, array()); + $this->assertEquals('curl -X POST -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); + + $result = $client->buildCurlCommand('http://foo.com', $data, array('key' => 'value')); + $this->assertEquals('curl -X POST -H \'key: value\' -d \'{"foo": "\'\\\'\'; ls;"}\' \'http://foo.com\' -m 5 -k > /dev/null 2>&1 &', $result); + } + + /** + * @covers Raven_Client::user_context + */ + public function testUserContextWithoutMerge() + { + $client = new Dummy_Raven_Client(); + $client->user_context(array('foo' => 'bar'), false); + $client->user_context(array('baz' => 'bar'), false); + $this->assertEquals(array('baz' => 'bar'), $client->context->user); + } + + /** + * @covers Raven_Client::user_context + */ + public function testUserContextWithMerge() + { + $client = new Dummy_Raven_Client(); + $client->user_context(array('foo' => 'bar'), true); + $client->user_context(array('baz' => 'bar'), true); + $this->assertEquals(array('foo' => 'bar', 'baz' => 'bar'), $client->context->user); + } + + /** + * @covers Raven_Client::user_context + */ + public function testUserContextWithMergeAndNull() + { + $client = new Dummy_Raven_Client(); + $client->user_context(array('foo' => 'bar'), true); + $client->user_context(null, true); + $this->assertEquals(array('foo' => 'bar'), $client->context->user); + } + + /** + * Set the server array to the test values, check the current url + * + * @dataProvider currentUrlProvider + * @param array $serverVars + * @param array $options + * @param string $expected - the url expected + * @param string $message - fail message + * @covers Raven_Client::get_current_url + * @covers Raven_Client::isHttps + */ + public function testCurrentUrl($serverVars, $options, $expected, $message) + { + $_SERVER = $serverVars; + + $client = new Dummy_Raven_Client($options); + $result = $client->test_get_current_url(); + + $this->assertSame($expected, $result, $message); + } + + /** + * Arrays of: + * $_SERVER data + * config + * expected url + * Fail message + * + * @return array + */ + public function currentUrlProvider() + { + return array( + array( + array(), + array(), + null, + 'No url expected for empty REQUEST_URI' + ), + array( + array( + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + ), + array(), + 'http://example.com/', + 'The url is expected to be http with the request uri' + ), + array( + array( + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on' + ), + array(), + 'https://example.com/', + 'The url is expected to be https because of HTTPS on' + ), + array( + array( + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'SERVER_PORT' => '443' + ), + array(), + 'https://example.com/', + 'The url is expected to be https because of the server port' + ), + array( + array( + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'X-FORWARDED-PROTO' => 'https' + ), + array(), + 'http://example.com/', + 'The url is expected to be http because the X-Forwarded header is ignored' + ), + array( + array( + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'example.com', + 'X-FORWARDED-PROTO' => 'https' + ), + array('trust_x_forwarded_proto' => true), + 'https://example.com/', + 'The url is expected to be https because the X-Forwarded header is trusted' + ) + ); + } + + /** + * @covers Raven_Client::uuid4() + */ + public function testUuid4() + { + $method = new ReflectionMethod('Raven_Client', 'uuid4'); + $method->setAccessible(true); + for ($i = 0; $i < 1000; $i++) { + $this->assertRegExp('/^[0-9a-z-]+$/', $method->invoke(null)); + } + } + + /** + * @covers Raven_Client::getEnvironment + * @covers Raven_Client::setEnvironment + * @covers Raven_Client::getRelease + * @covers Raven_Client::setRelease + * @covers Raven_Client::getAppPath + * @covers Raven_Client::setAppPath + * @covers Raven_Client::getExcludedAppPaths + * @covers Raven_Client::setExcludedAppPaths + * @covers Raven_Client::getPrefixes + * @covers Raven_Client::setPrefixes + * @covers Raven_Client::getSendCallback + * @covers Raven_Client::setSendCallback + * @covers Raven_Client::getTransport + * @covers Raven_Client::setTransport + * @covers Raven_Client::getServerEndpoint + * @covers Raven_Client::getLastError + * @covers Raven_Client::getLastEventID + * @covers Raven_Client::get_extra_data + * @covers Raven_Client::setProcessors + * @covers Raven_Client::getLastSentryError + * @covers Raven_Client::getShutdownFunctionHasBeenSet + */ + public function testGettersAndSetters() + { + $client = new Dummy_Raven_Client(); + $property_method__convert_path = new ReflectionMethod('Raven_Client', '_convertPath'); + $property_method__convert_path->setAccessible(true); + $callable = array($this, 'stabClosureVoid'); + + $data = array( + array('environment', null, 'value', ), + array('environment', null, null, ), + array('release', null, 'value', ), + array('release', null, null, ), + array('app_path', null, 'value', $property_method__convert_path->invoke($client, 'value')), + array('app_path', null, null, ), + array('app_path', null, false, null, ), + array('excluded_app_paths', null, null), + array('excluded_app_paths', null, array(), null), + array('excluded_app_paths', null, array(__FILE__), array(__FILE__)), + array('excluded_app_paths', null, array(__DIR__), array(__DIR__ . DIRECTORY_SEPARATOR)), + array('prefixes', null, array('value'), array($property_method__convert_path->invoke($client, 'value'))), + array('prefixes', null, array()), + array('send_callback', null, $callable), + array('send_callback', null, null), + array('transport', null, $callable), + array('transport', null, null), + array('server', 'ServerEndpoint', 'http://example.com/'), + array('server', 'ServerEndpoint', 'http://example.org/'), + array('_lasterror', null, null, ), + array('_lasterror', null, 'value', ), + array('_lasterror', null, mt_rand(100, 999), ), + array('_last_sentry_error', null, (object)array('error' => 'test',), ), + array('_last_event_id', null, mt_rand(100, 999), ), + array('_last_event_id', null, 'value', ), + array('extra_data', '_extra_data', array('key' => 'value'), ), + array('processors', 'processors', array(), ), + array('processors', 'processors', array('key' => 'value'), ), + array('_shutdown_function_has_been_set', null, true), + array('_shutdown_function_has_been_set', null, false), + ); + foreach ($data as &$datum) { + $this->subTestGettersAndSettersDatum($client, $datum); + } + foreach ($data as &$datum) { + $client = new Dummy_Raven_Client(); + $this->subTestGettersAndSettersDatum($client, $datum); + } + } + + private function subTestGettersAndSettersDatum(Raven_Client $client, $datum) + { + if (count($datum) == 3) { + list($property_name, $function_name, $value_in) = $datum; + $value_out = $value_in; + } else { + list($property_name, $function_name, $value_in, $value_out) = $datum; + } + if (is_null($function_name)) { + $function_name = str_replace('_', '', $property_name); + } + + $method_get_name = 'get'.$function_name; + $method_set_name = 'set'.$function_name; + $property = new ReflectionProperty('Raven_Client', $property_name); + $property->setAccessible(true); + + if (method_exists($client, $method_set_name)) { + $setter_output = $client->$method_set_name($value_in); + if (!is_null($setter_output) and is_object($setter_output)) { + // chaining call test + $this->assertEquals(spl_object_hash($client), spl_object_hash($setter_output)); + } + $actual_value = $property->getValue($client); + $this->assertMixedValueAndArray($value_out, $actual_value); + } + + if (method_exists($client, $method_get_name)) { + $property->setValue($client, $value_out); + $reflection = new ReflectionMethod('Raven_Client', $method_get_name); + if ($reflection->isPublic()) { + $actual_value = $client->$method_get_name(); + $this->assertMixedValueAndArray($value_out, $actual_value); + } + } + } + + private function assertMixedValueAndArray($expected_value, $actual_value) + { + if (is_null($expected_value)) { + $this->assertNull($actual_value); + } elseif ($expected_value === true) { + $this->assertTrue($actual_value); + } elseif ($expected_value === false) { + $this->assertFalse($actual_value); + } elseif (is_string($expected_value) or is_integer($expected_value) or is_double($expected_value)) { + $this->assertEquals($expected_value, $actual_value); + } elseif (is_array($expected_value)) { + $this->assertInternalType('array', $actual_value); + $this->assertEquals(count($expected_value), count($actual_value)); + foreach ($expected_value as $key => $value) { + $this->assertArrayHasKey($key, $actual_value); + $this->assertMixedValueAndArray($value, $actual_value[$key]); + } + } elseif (is_callable($expected_value) or is_object($expected_value)) { + $this->assertEquals(spl_object_hash($expected_value), spl_object_hash($actual_value)); + } + } + + /** + * @covers Raven_Client::_convertPath + */ + public function test_convertPath() + { + $property = new ReflectionMethod('Raven_Client', '_convertPath'); + $property->setAccessible(true); + + $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar')); + $this->assertEquals('/foo/bar/', $property->invoke(null, '/foo/bar/')); + $this->assertEquals('foo/bar', $property->invoke(null, 'foo/bar')); + $this->assertEquals('foo/bar/', $property->invoke(null, 'foo/bar/')); + $this->assertEquals(dirname(__DIR__).'/', $property->invoke(null, __DIR__.'/../')); + $this->assertEquals(dirname(dirname(__DIR__)).'/', $property->invoke(null, __DIR__.'/../../')); + } + + /** + * @covers Raven_Client::getDefaultProcessors + */ + public function testGetDefaultProcessors() + { + foreach (Raven_Client::getDefaultProcessors() as $class_name) { + $this->assertInternalType('string', $class_name); + $this->assertTrue(class_exists($class_name)); + $reflection = new ReflectionClass($class_name); + $this->assertTrue($reflection->isSubclassOf('Raven_Processor')); + $this->assertFalse($reflection->isAbstract()); + } + } + + /** + * @covers Raven_Client::get_default_ca_cert + */ + public function testGet_default_ca_cert() + { + $reflection = new ReflectionMethod('Raven_Client', 'get_default_ca_cert'); + $reflection->setAccessible(true); + $this->assertFileExists($reflection->invoke(null)); + } + + /** + * @covers Raven_Client::translateSeverity + * @covers Raven_Client::registerSeverityMap + */ + public function testTranslateSeverity() + { + $reflection = new ReflectionProperty('Raven_Client', 'severity_map'); + $reflection->setAccessible(true); + $client = new Dummy_Raven_Client(); + + $predefined = 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, ); + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + $predefined[] = E_DEPRECATED; + $predefined[] = E_USER_DEPRECATED; + } + $predefined_values = array('debug', 'info', 'warning', 'warning', 'error', 'fatal', ); + + // step 1 + foreach ($predefined as &$key) { + $this->assertContains($client->translateSeverity($key), $predefined_values); + } + $this->assertEquals('error', $client->translateSeverity(123456)); + // step 2 + $client->registerSeverityMap(array()); + $this->assertMixedValueAndArray(array(), $reflection->getValue($client)); + foreach ($predefined as &$key) { + $this->assertContains($client->translateSeverity($key), $predefined_values); + } + $this->assertEquals('error', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123456)); + // step 3 + $client->registerSeverityMap(array(123456 => 'foo', )); + $this->assertMixedValueAndArray(array(123456 => 'foo'), $reflection->getValue($client)); + foreach ($predefined as &$key) { + $this->assertContains($client->translateSeverity($key), $predefined_values); + } + $this->assertEquals('foo', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123457)); + // step 4 + $client->registerSeverityMap(array(E_USER_ERROR => 'bar', )); + $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); + $this->assertEquals('error', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123457)); + // step 5 + $client->registerSeverityMap(array(E_USER_ERROR => 'bar', 123456 => 'foo', )); + $this->assertEquals('bar', $client->translateSeverity(E_USER_ERROR)); + $this->assertEquals('foo', $client->translateSeverity(123456)); + $this->assertEquals('error', $client->translateSeverity(123457)); + } + + /** + * @covers Raven_Client::getUserAgent + */ + public function testGetUserAgent() + { + $this->assertRegExp('|^[0-9a-z./_-]+$|i', Raven_Client::getUserAgent()); + } + + public function testCaptureExceptionWithLogger() + { + $client = new Dummy_Raven_Client(); + $client->captureException(new Exception(), null, 'foobar'); + + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + $this->assertEquals('foobar', $event['logger']); + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::send + * @covers Raven_Client::send_remote + * @covers Raven_Client::send_http + */ + public function testCurl_method() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $client->captureMessage('foobar'); + $this->assertTrue($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + + // step 2 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'exec', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $client->captureMessage('foobar'); + $this->assertFalse($client->_send_http_synchronous); + $this->assertTrue($client->_send_http_asynchronous_curl_exec_called); + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::send + * @covers Raven_Client::send_remote + * @covers Raven_Client::send_http + */ + public function testCurl_method_async() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'async', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $object = $client->get_curl_handler(); + $this->assertInternalType('object', $object); + $this->assertEquals('Raven_CurlHandler', get_class($object)); + + $reflection = new ReflectionProperty('Raven_CurlHandler', 'options'); + $reflection->setAccessible(true); + $this->assertEquals($reflection->getValue($object), $client->get_curl_options()); + + // step 2 + $ch = new Dummy_Raven_CurlHandler(); + $client->set_curl_handler($ch); + $client->captureMessage('foobar'); + $this->assertFalse($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertTrue($ch->_enqueue_called); + } + + /** + * @backupGlobals + * @covers Raven_Client::__construct + */ + public function testConstructWithServerDSN() + { + $_SERVER['SENTRY_DSN'] = 'http://public:secret@example.com/1'; + $client = new Dummy_Raven_Client(); + $this->assertEquals(1, $client->project); + $this->assertEquals('http://example.com/api/1/store/', $client->server); + $this->assertEquals('public', $client->public_key); + $this->assertEquals('secret', $client->secret_key); + } + + /** + * @backupGlobals + * @covers Raven_Client::_server_variable + */ + public function test_server_variable() + { + $method = new ReflectionMethod('Raven_Client', '_server_variable'); + $method->setAccessible(true); + foreach ($_SERVER as $key => $value) { + $actual = $method->invoke(null, $key); + $this->assertNotNull($actual); + $this->assertEquals($value, $actual); + } + foreach (array('foo', 'bar', 'foobar', '123456', 'SomeLongNonExistedKey') as $key => $value) { + if (!isset($_SERVER[$key])) { + $actual = $method->invoke(null, $key); + $this->assertNotNull($actual); + $this->assertEquals('', $actual); + } + unset($_SERVER[$key]); + $actual = $method->invoke(null, $key); + $this->assertNotNull($actual); + $this->assertEquals('', $actual); + } + } + + public function testEncodeTooDepth() + { + $client = new Dummy_Raven_Client(); + $data_broken = array(); + for ($i = 0; $i < 1024; $i++) { + $data_broken = array($data_broken); + } + $value = $client->encode($data_broken); + if (!function_exists('json_encode') or version_compare(PHP_VERSION, '5.5.0', '>=')) { + $this->assertFalse($value, 'Broken data encoded successfully with '. + (function_exists('json_encode') ? 'native method' : 'Raven_Compat::_json_encode')); + } else { + if ($value !== false) { + $this->markTestSkipped(); + } else { + $this->assertEquals('eJyLjh4Fo2AUjFgQOwpGwSgYuQAA3Q7g1w==', $value, 'Native json_encode error'); + } + } + } + + public function testEncode() + { + $client = new Dummy_Raven_Client(); + $data = array('some' => (object)array('value' => 'data'), 'foo' => array('bar', null, 123), false); + $json_stringify = Raven_Compat::json_encode($data); + $value = $client->encode($data); + $this->assertNotFalse($value); + $this->assertRegExp('_^[a-zA-Z0-9/=]+$_', $value, 'Raven_Client::encode returned malformed data'); + $decoded = base64_decode($value); + $this->assertInternalType('string', $decoded, 'Can not use base64 decode on the encoded blob'); + if (function_exists("gzcompress")) { + $decoded = gzuncompress($decoded); + $this->assertEquals($json_stringify, $decoded, 'Can not decompress compressed blob'); + } else { + $this->assertEquals($json_stringify, $decoded); + } + } + + /** + * @covers Raven_Client::__construct + * @covers Raven_Client::registerDefaultBreadcrumbHandlers + */ + public function testRegisterDefaultBreadcrumbHandlers() + { + if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { + $this->markTestSkipped('HHVM stacktrace behaviour'); + return; + } + $previous = set_error_handler(array($this, 'stabClosureErrorHandler'), E_USER_NOTICE); + new Raven_Client(null, array()); + $this->_closure_called = false; + trigger_error('foobar', E_USER_NOTICE); + $u = $this->_closure_called; + $debug_backtrace = $this->_debug_backtrace; + set_error_handler($previous, E_ALL); + $this->assertTrue($u); + if (isset($debug_backtrace[1]['function']) and ($debug_backtrace[1]['function'] == 'call_user_func') + and version_compare(PHP_VERSION, '7.0', '>=') + ) { + $offset = 2; + } elseif (version_compare(PHP_VERSION, '7.0', '>=')) { + $offset = 1; + } else { + $offset = 2; + } + $this->assertEquals('Raven_Breadcrumbs_ErrorHandler', $debug_backtrace[$offset]['class']); + } + + private $_closure_called = false; + + public function stabClosureVoid() + { + $this->_closure_called = true; + } + + public function stabClosureNull() + { + $this->_closure_called = true; + + return null; + } + + public function stabClosureFalse() + { + $this->_closure_called = true; + + return false; + } + + private $_debug_backtrace = array(); + + public function stabClosureErrorHandler($code, $message, $file = '', $line = 0, $context = array()) + { + $this->_closure_called = true; + $this->_debug_backtrace = debug_backtrace(); + + return true; + } + + /** + * @covers Raven_Client::onShutdown + * @covers Raven_Client::sendUnsentErrors + * @covers Raven_Client::capture + */ + public function testOnShutdown() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $this->assertEquals(0, count($client->_pending_events)); + $client->_pending_events[] = array('foo' => 'bar'); + $client->sendUnsentErrors(); + $this->assertTrue($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertEquals(0, count($client->_pending_events)); + + // step 2 + $client->_send_http_synchronous = false; + $client->_send_http_asynchronous_curl_exec_called = false; + + $client->store_errors_for_bulk_send = true; + $client->captureMessage('foobar'); + $this->assertEquals(1, count($client->_pending_events)); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + $client->_send_http_synchronous = false; + $client->_send_http_asynchronous_curl_exec_called = false; + + // step 3 + $client->onShutdown(); + $this->assertTrue($client->_send_http_synchronous); + $this->assertFalse($client->_send_http_asynchronous_curl_exec_called); + $this->assertEquals(0, count($client->_pending_events)); + + // step 1 + $client = null; + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'async', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $ch = new Dummy_Raven_CurlHandler(); + $client->set_curl_handler($ch); + $client->captureMessage('foobar'); + $client->onShutdown(); + $client = null; + $this->assertTrue($ch->_join_called); + } + + /** + * @covers Raven_Client::send + */ + public function testNonWorkingSendSendCallback() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $this->_closure_called = false; + $client->setSendCallback(array($this, 'stabClosureNull')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertTrue($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + // step 2 + $this->_closure_called = false; + $client->_send_http_synchronous = false; + $client->_send_http_asynchronous_curl_exec_called = false; + $client->setSendCallback(array($this, 'stabClosureFalse')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } + + /** + * @covers Raven_Client::send + */ + public function testNonWorkingSendDSNEmpty() + { + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $client->server = null; + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } + + /** + * @covers Raven_Client::send + */ + public function testNonWorkingSendSetTransport() + { + // step 1 + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'foobar', + 'install_default_breadcrumb_handlers' => false, + ) + ); + $this->_closure_called = false; + $client->setTransport(array($this, 'stabClosureNull')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + // step 2 + $this->_closure_called = false; + $client->setSendCallback(array($this, 'stabClosureFalse')); + $this->assertFalse($this->_closure_called); + $data = array('foo' => 'bar'); + $client->send($data); + $this->assertTrue($this->_closure_called); + $this->assertFalse($client->_send_http_synchronous or $client->_send_http_asynchronous_curl_exec_called); + } + + /** + * @covers Raven_Client::__construct + */ + public function test__construct_handlers() + { + foreach (array(true, false) as $u1) { + foreach (array(true, false) as $u2) { + $client = new Dummy_Raven_Client( + null, array( + 'install_default_breadcrumb_handlers' => $u1, + 'install_shutdown_handler' => $u2, + ) + ); + $this->assertEquals($u1, $client->dummy_breadcrumbs_handlers_has_set); + $this->assertEquals($u2, $client->dummy_shutdown_handlers_has_set); + } + } + } + + /** + * @covers Raven_Client::__destruct + * @covers Raven_Client::close_all_children_link + */ + public function test__destruct_calls_close_functions() + { + $client = new Dummy_Raven_Client_With_Overrided_Direct_Send( + 'http://public:secret@example.com/1', array( + 'install_default_breadcrumb_handlers' => false, + 'install_shutdown_handler' => false, + ) + ); + $client::$_close_curl_resource_called = false; + $client->close_all_children_link(); + unset($client); + $this->assertTrue(Dummy_Raven_Client_With_Overrided_Direct_Send::$_close_curl_resource_called); + } + + /** + * @covers Raven_Client::get_user_data + * @backupGlobals + */ + public function testGet_user_data_step1() + { + // step 1 + $client = new Dummy_Raven_Client(); + $output = $client->get_user_data(); + $this->assertInternalType('array', $output); + $this->assertArrayHasKey('user', $output); + $this->assertArrayHasKey('id', $output['user']); + } + + /** + * @covers Raven_Client::get_user_data + * @backupGlobals + */ + public function testGet_user_data_step2() + { + if (version_compare(PHP_VERSION, '7.1.999', '>')) { + /** + * @doc https://3v4l.org/OVbja + * @doc https://3v4l.org/uT00O + * @doc https://github.com/php/php-src/blob/316802d8f2b07b863f1596cd804db28a183556e5/NEWS#L87 + */ + $this->markTestSkipped('PHP 7.2 does not support clearing session id'); + } + $client = new Dummy_Raven_Client(); + $session_id = session_id(); + session_write_close(); + @session_id(''); + $output = $client->get_user_data(); + session_id($session_id); + $this->assertInternalType('array', $output); + $this->assertEquals(0, count($output)); + } + + /** + * @covers Raven_Client::get_user_data + * @backupGlobals + */ + public function testGet_user_data_step3() + { + $client = new Dummy_Raven_Client(); + @session_start(array('use_cookies' => false)); + $_SESSION = array('foo' => 'bar'); + $output = $client->get_user_data(); + $this->assertInternalType('array', $output); + $this->assertArrayHasKey('user', $output); + $this->assertArrayHasKey('id', $output['user']); + $this->assertArrayHasKey('data', $output['user']); + $this->assertArrayHasKey('foo', $output['user']['data']); + $this->assertEquals('bar', $output['user']['data']['foo']); + } + + /** + * @covers Raven_Client::capture + * @covers Raven_Client::setRelease + * @covers Raven_Client::setEnvironment + */ + public function testCaptureLevel() + { + foreach (array(Raven_Client::MESSAGE_LIMIT * 3, 100) as $length) { + $message = ''; + for ($i = 0; $i < $length; $i++) { + $message .= chr($i % 256); + } + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => $message, )); + $events = $client->getSentEvents(); + $this->assertEquals(1, count($events)); + $event = array_pop($events); + + $this->assertEquals('error', $event['level']); + $this->assertEquals(substr($message, 0, min(Raven_Client::MESSAGE_LIMIT, $length)), $event['message']); + $this->assertArrayNotHasKey('release', $event); + $this->assertArrayNotHasKey('environment', $event); + } + + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => 'foobar')); + $events = $client->getSentEvents(); + $event = array_pop($events); + $input = $client->get_http_data(); + $this->assertEquals($input['request'], $event['request']); + $this->assertArrayNotHasKey('release', $event); + $this->assertArrayNotHasKey('environment', $event); + + $client = new Dummy_Raven_Client(); + $client->capture(array('message' => 'foobar', 'request' => array('foo' => 'bar'), )); + $events = $client->getSentEvents(); + $event = array_pop($events); + $this->assertEquals(array('foo' => 'bar'), $event['request']); + $this->assertArrayNotHasKey('release', $event); + $this->assertArrayNotHasKey('environment', $event); + + foreach (array(false, true) as $u1) { + foreach (array(false, true) as $u2) { + $client = new Dummy_Raven_Client(); + if ($u1) { + $client->setRelease('foo'); + } + if ($u2) { + $client->setEnvironment('bar'); + } + $client->capture(array('message' => 'foobar', )); + $events = $client->getSentEvents(); + $event = array_pop($events); + if ($u1) { + $this->assertEquals('foo', $event['release']); + } else { + $this->assertArrayNotHasKey('release', $event); + } + if ($u2) { + $this->assertEquals('bar', $event['environment']); + } else { + $this->assertArrayNotHasKey('environment', $event); + } + } + } + } + + /** + * @covers Raven_Client::capture + */ + public function testCaptureNoUserAndRequest() + { + $client = new Dummy_Raven_Client_No_Http(null, array( + 'install_default_breadcrumb_handlers' => false, + )); + $session_id = session_id(); + session_write_close(); + @session_id(''); + $client->capture(array('user' => '', 'request' => '')); + $events = $client->getSentEvents(); + $event = array_pop($events); + $this->assertArrayNotHasKey('user', $event); + $this->assertArrayNotHasKey('request', $event); + + // step 3 + @session_id($session_id); + @session_start(array('use_cookies' => false, )); + } + + /** + * @covers Raven_Client::capture + */ + public function testCaptureNonEmptyBreadcrumb() + { + $client = new Dummy_Raven_Client(); + $ts1 = microtime(true); + $client->breadcrumbs->record(array('foo' => 'bar')); + $client->breadcrumbs->record(array('honey' => 'clover')); + $client->capture(array()); + $events = $client->getSentEvents(); + $event = array_pop($events); + foreach ($event['breadcrumbs'] as &$crumb) { + $this->assertGreaterThanOrEqual($ts1, $crumb['timestamp']); + unset($crumb['timestamp']); + } + $this->assertEquals(array( + array('foo' => 'bar'), + array('honey' => 'clover'), + ), $event['breadcrumbs']); + } + + + /** + * @covers Raven_Client::capture + */ + public function testCaptureAutoLogStacks() + { + $client = new Dummy_Raven_Client(); + $client->capture(array('auto_log_stacks' => true), true); + $events = $client->getSentEvents(); + $event = array_pop($events); + $this->assertArrayHasKey('stacktrace', $event); + $this->assertInternalType('array', $event['stacktrace']['frames']); + } + + /** + * @covers Raven_Client::send_http_asynchronous_curl_exec + */ + public function testSend_http_asynchronous_curl_exec() + { + $client = new Dummy_Raven_Client_With_Sync_Override( + 'http://public:secret@example.com/1', array( + 'curl_method' => 'exec', + 'install_default_breadcrumb_handlers' => false, + ) + ); + if (file_exists(Dummy_Raven_Client_With_Sync_Override::test_filename())) { + unlink(Dummy_Raven_Client_With_Sync_Override::test_filename()); + } + $client->captureMessage('foobar'); + $test_data = Dummy_Raven_Client_With_Sync_Override::get_test_data(); + $this->assertStringEqualsFile(Dummy_Raven_Client_With_Sync_Override::test_filename(), $test_data."\n"); + } + + /** + * @covers Raven_Client::close_curl_resource + */ + public function testClose_curl_resource() + { + $raven = new Dummy_Raven_Client(); + $reflection = new ReflectionProperty('Raven_Client', '_curl_instance'); + $reflection->setAccessible(true); + $ch = curl_init(); + $reflection->setValue($raven, $ch); + unset($ch); + + $this->assertInternalType('resource', $reflection->getValue($raven)); + $raven->close_curl_resource(); + $this->assertNull($reflection->getValue($raven)); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/CompatTest.php b/Vendor/sentry/test/Raven/Tests/CompatTest.php new file mode 100644 index 0000000..a813979 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/CompatTest.php @@ -0,0 +1,113 @@ +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); + + $long_key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'; + $result = Raven_Compat::_hash_hmac('md5', 'data', $long_key); + $this->assertEquals('951038f9ab8a10c929ab6dbc5f927207', $result); + + $result = Raven_Compat::_hash_hmac('sha1', 'data', $long_key); + $this->assertEquals('cbf0d1ca10d211da2bc15cb3b579ecfebf3056d2', $result); + + $result = Raven_Compat::_hash_hmac('md5', 'foobar', $long_key); + $this->assertEquals('5490f3cddeb9665bce3239cbc4c15e2c', $result); + + $result = Raven_Compat::_hash_hmac('sha1', 'foobar', $long_key); + $this->assertEquals('5729f50ff2fbb8f8bf81d7a86f69a89f7574697c', $result); + + + $result = Raven_Compat::_hash_hmac('md5', 'foo', $long_key); + $this->assertEquals('ab193328035cbd3a48dea9d64ba92736', $result); + + $result = Raven_Compat::_hash_hmac('sha1', 'foo', $long_key); + $this->assertEquals('8f883d0755115314930968496573f27735eb0c41', $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); + + $result = Raven_Compat::_json_encode(array(null, false, true, 1.5)); + $this->assertEquals('[null,false,true,1.5]', $result); + } + + /** + * @covers Raven_Compat::_json_encode + * @covers Raven_Compat::_json_encode_lowlevel + * + * I show you how deep the rabbit hole goes + */ + public function test_json_encode_with_broken_data() + { + $data_broken_named = array(); + $data_broken_named_510 = null; + $data_broken_named_511 = null; + + $data_broken = array(); + $data_broken_510 = null; + $data_broken_511 = null; + for ($i = 0; $i < 1024; $i++) { + $data_broken = array($data_broken); + $data_broken_named = array('a' => $data_broken_named); + switch ($i) { + case 510: + $data_broken_510 = $data_broken; + $data_broken_named_510 = $data_broken_named; + break; + case 511: + $data_broken_511 = $data_broken; + $data_broken_named_511 = $data_broken_named; + break; + } + } + $value_1024 = Raven_Compat::_json_encode($data_broken); + $value_510 = Raven_Compat::_json_encode($data_broken_510); + $value_511 = Raven_Compat::_json_encode($data_broken_511); + $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); + $this->assertNotFalse($value_510); + $this->assertFalse($value_511); + + $value_1024 = Raven_Compat::_json_encode($data_broken_named); + $value_510 = Raven_Compat::_json_encode($data_broken_named_510); + $value_511 = Raven_Compat::_json_encode($data_broken_named_511); + $this->assertFalse($value_1024, 'Broken data encoded successfully with Raven_Compat::_json_encode'); + $this->assertNotFalse($value_510); + $this->assertFalse($value_511); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/ErrorHandlerTest.php b/Vendor/sentry/test/Raven/Tests/ErrorHandlerTest.php new file mode 100644 index 0000000..367bc6a --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/ErrorHandlerTest.php @@ -0,0 +1,246 @@ +errorLevel = error_reporting(); + $this->errorHandlerCalled = false; + $this->existingErrorHandler = set_error_handler(array($this, 'errorHandler'), -1); + // improves the reliability of tests + if (function_exists('error_clear_last')) { + error_clear_last(); + } + } + + public function errorHandler() + { + $this->errorHandlerCalled = true; + } + + public function tearDown() + { + restore_exception_handler(); + set_error_handler($this->existingErrorHandler); + // // XXX(dcramer): this isn't great as it doesnt restore the old error reporting level + // set_error_handler(array($this, 'errorHandler'), error_reporting()); + error_reporting($this->errorLevel); + } + + public function testErrorsAreLoggedAsExceptions() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException', 'sendUnsentErrors')) + ->getMock(); + $client->expects($this->once()) + ->method('captureException') + ->with($this->isInstanceOf('ErrorException')); + + $handler = new Raven_ErrorHandler($client, E_ALL); + $handler->handleError(E_WARNING, 'message'); + } + + public function testExceptionsAreLogged() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $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 testErrorHandlerPassErrorReportingPass() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->once()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(false, -1); + + error_reporting(E_USER_WARNING); + trigger_error('Warning', E_USER_WARNING); + } + + public function testErrorHandlerPropagates() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->never()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(true, E_DEPRECATED); + + error_reporting(E_USER_WARNING); + trigger_error('Warning', E_USER_WARNING); + + $this->assertEquals($this->errorHandlerCalled, 1); + } + + public function testExceptionHandlerPropagatesToNative() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->exactly(2)) + ->method('captureException') + ->with($this->isInstanceOf('Exception')); + + $handler = new Raven_ErrorHandler($client); + + set_exception_handler(null); + $handler->registerExceptionHandler(false); + + $testException = new Exception('Test exception'); + + $didRethrow = false; + try { + $handler->handleException($testException); + } catch (Exception $e) { + $didRethrow = true; + } + + $this->assertFalse($didRethrow); + + set_exception_handler(null); + $handler->registerExceptionHandler(true); + + $didRethrow = false; + $rethrownException = null; + try { + $handler->handleException($testException); + } catch (Exception $e) { + $didRethrow = true; + $rethrownException = $e; + } + + $this->assertTrue($didRethrow); + $this->assertSame($testException, $rethrownException); + } + + public function testErrorHandlerRespectsErrorReportingDefault() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->once()) + ->method('captureException'); + + error_reporting(E_DEPRECATED); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(true); + + error_reporting(E_ALL); + trigger_error('Warning', E_USER_WARNING); + + $this->assertEquals($this->errorHandlerCalled, 1); + } + + // Because we cannot **know** that a user silenced an error, we always + // defer to respecting the error reporting settings. + public function testSilentErrorsAreNotReportedWithGlobal() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->never()) + ->method('captureException'); + + error_reporting(E_ALL); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(true); + + @$undefined; + + // also ensure it doesnt get reported by the fatal handler + $handler->handleFatalError(); + } + + // Because we cannot **know** that a user silenced an error, we always + // defer to respecting the error reporting settings. + public function testSilentErrorsAreNotReportedWithLocal() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->never()) + ->method('captureException'); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(true, E_ALL); + + @$my_array[2]; + + // also ensure it doesnt get reported by the fatal handler + $handler->handleFatalError(); + } + + public function testShouldCaptureFatalErrorBehavior() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $handler = new Raven_ErrorHandler($client); + + $this->assertEquals($handler->shouldCaptureFatalError(E_ERROR), true); + + $this->assertEquals($handler->shouldCaptureFatalError(E_WARNING), false); + } + + public function testErrorHandlerDefaultsErrorReporting() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $client->expects($this->never()) + ->method('captureException'); + + error_reporting(E_USER_ERROR); + + $handler = new Raven_ErrorHandler($client); + $handler->registerErrorHandler(false); + + trigger_error('Warning', E_USER_WARNING); + } + + public function testFluidInterface() + { + $client = $this->getMockBuilder('Client') + ->setMethods(array('captureException')) + ->getMock(); + $handler = new Raven_ErrorHandler($client); + $result = $handler->registerErrorHandler(); + $this->assertEquals($result, $handler); + $result = $handler->registerExceptionHandler(); + $this->assertEquals($result, $handler); + // TODO(dcramer): cant find a great way to test resetting the shutdown + // handler + // $result = $handler->registerShutdownHandler(); + // $this->assertEquals($result, $handler); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/IntegrationTest.php b/Vendor/sentry/test/Raven/Tests/IntegrationTest.php new file mode 100644 index 0000000..5464af8 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/IntegrationTest.php @@ -0,0 +1,69 @@ +__sent_events; + } + public function send(&$data) + { + if (is_callable($this->send_callback) && call_user_func_array($this->send_callback, array(&$data)) === false) { + // if send_callback returns falsely, end native send + return; + } + $this->__sent_events[] = $data; + } + public static function is_http_request() + { + return true; + } + // short circuit breadcrumbs + public function registerDefaultBreadcrumbHandlers() + { + } +} + +class Raven_Tests_IntegrationTest extends \PHPUnit\Framework\TestCase +{ + 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 testCaptureSimpleError() + { + $client = new DummyIntegration_Raven_Client('https://public:secret@example.com/1'); + + @mkdir('/no/way'); + + $client->captureLastError(); + + $events = $client->getSentEvents(); + $event = array_pop($events); + + $exc = $event['exception']['values'][0]; + $this->assertEquals($exc['value'], 'mkdir(): No such file or directory'); + $stack = $exc['stacktrace']['frames']; + $lastFrame = $stack[count($stack) - 1]; + $this->assertEquals(@$lastFrame['filename'], 'test/Raven/Tests/IntegrationTest.php'); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/Processor/RemoveCookiesProcessorTest.php b/Vendor/sentry/test/Raven/Tests/Processor/RemoveCookiesProcessorTest.php new file mode 100644 index 0000000..38354b7 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/Processor/RemoveCookiesProcessorTest.php @@ -0,0 +1,78 @@ +getMockBuilder('\Raven_Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new Raven_Processor_RemoveCookiesProcessor($client); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'foo' => 'bar', + ), + ), + array( + 'request' => array( + 'foo' => 'bar', + ), + ), + ), + array( + array( + 'request' => array( + 'foo' => 'bar', + 'cookies' => 'baz', + 'headers' => array( + 'Cookie' => 'bar', + 'AnotherHeader' => 'foo', + ), + ), + ), + array( + 'request' => array( + 'foo' => 'bar', + 'cookies' => Raven_Processor::STRING_MASK, + 'headers' => array( + 'Cookie' => Raven_Processor::STRING_MASK, + 'AnotherHeader' => 'foo', + ), + ), + ), + ), + ); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php b/Vendor/sentry/test/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php new file mode 100644 index 0000000..025a5bc --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/Processor/RemoveHttpBodyProcessorTest.php @@ -0,0 +1,121 @@ +getMockBuilder('\Raven_Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new Raven_Processor_RemoveHttpBodyProcessor($client); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'method' => 'POST', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'PUT', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'PATCH', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'DELETE', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => Raven_Processor::STRING_MASK, + ), + ), + ), + array( + array( + 'request' => array( + 'method' => 'GET', + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'data' => array( + 'foo' => 'bar', + ), + ), + ), + ), + ); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/Processor/SanitizeDataProcessorTest.php b/Vendor/sentry/test/Raven/Tests/Processor/SanitizeDataProcessorTest.php new file mode 100644 index 0000000..94267df --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/Processor/SanitizeDataProcessorTest.php @@ -0,0 +1,251 @@ + 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 Dummy_Raven_Client(); + $processor = new Raven_Processor_SanitizeDataProcessor($client); + $processor->process($data); + + $vars = $data['request']['data']; + $this->assertEquals($vars['foo'], 'bar'); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['password']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['the_secret']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['a_password_here']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['mypasswd']); + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['authorization']); + + $this->markTestIncomplete('Array scrubbing has not been implemented yet.'); + + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $vars['card_number']['0']); + } + + public function testDoesFilterSessionId() + { + $data = array( + 'request' => array( + 'cookies' => array( + ini_get('session.name') => 'abc', + ), + ) + ); + + $client = new Dummy_Raven_Client(); + $processor = new Raven_Processor_SanitizeDataProcessor($client); + $processor->process($data); + + $cookies = $data['request']['cookies']; + $this->assertEquals($cookies[ini_get('session.name')], Raven_Processor_SanitizeDataProcessor::STRING_MASK); + } + + public function testDoesFilterCreditCard() + { + $data = array( + 'extra' => array( + 'ccnumba' => '4242424242424242', + ), + ); + + $client = new Dummy_Raven_Client(); + $processor = new Raven_Processor_SanitizeDataProcessor($client); + $processor->process($data); + + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $data['extra']['ccnumba']); + } + + public function testSettingProcessorOptions() + { + $client = new Dummy_Raven_Client(); + $processor = new Raven_Processor_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 Dummy_Raven_Client($dsn, $client_options); + /** + * @var Raven_Processor_SanitizeDataProcessor $processor + */ + $processor = $client->processors[0]; + + $this->assertInstanceOf('Raven_Processor_SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_Processor_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_Processor_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( + 'request' => 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 Dummy_Raven_Client($dsn, $client_options); + /** + * @var Raven_Processor_SanitizeDataProcessor $processor + */ + $processor = $client->processors[0]; + + $this->assertInstanceOf('Raven_Processor_SanitizeDataProcessor', $processor); + $this->assertEquals($processor->getFieldsRe(), $processorOptions['Raven_Processor_SanitizeDataProcessor']['fields_re'], 'overwrote fields'); + $this->assertEquals($processor->getValuesRe(), $processorOptions['Raven_Processor_SanitizeDataProcessor']['values_re'], 'overwrote values'); + + $processor->process($data); + + $vars = $data['request']['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_Processor_SanitizeDataProcessor::STRING_MASK, $vars['api_token'], 'masked api_token'); + + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_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_Processor_SanitizeDataProcessor' => array( + 'fields_re' => '/(api_token)/i', + 'values_re' => '/^(?:\d[ -]*?){15,16}$/' + ) + ); + + $client_options = array( + 'processors' => array('Raven_Processor_SanitizeDataProcessor'), + 'processorOptions' => $processorOptions + ); + + $dsn = 'http://9aaa31f9a05b4e72aaa06aa8157a827a:9aa7aa82a9694a08a1a7589a2a035a9a@sentry.domain.tld/1'; + + return array( + array($processorOptions, $client_options, $dsn) + ); + } + + public function testDoesFilterExceptionDataWithMultipleValues() + { + // Prerequisite: create an array with an 'exception' that contains 2 entry for 'values' key both containing at + // least 1 key that must be masked (i.e. 'password') in one of their 'vars' array in 'frames'. + $data = array( + 'exception' => array( + 'values' => array( + array( + 'stacktrace' => array( + 'frames' => array( + array( + 'vars' => array( + 'credentials' => array( + 'password' => 'secretPassword' + ), + ), + ), + ), + ), + ), + array( + 'stacktrace' => array( + 'frames' => array( + array( + 'vars' => array( + 'credentials' => array( + 'password' => 'anotherSecretPassword' + ), + ), + ), + ), + ), + ), + ), + ), + ); + + $client = new Dummy_Raven_Client(); + $processor = new Raven_Processor_SanitizeDataProcessor($client); + // Action + $processor->process($data); + + // Expectation: make sure we mask password in both the values array + $passwordValue0 = $data['exception']['values'][0]['stacktrace']['frames'][0]['vars']['credentials']['password']; + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $passwordValue0); + $passwordValue1 = $data['exception']['values'][1]['stacktrace']['frames'][0]['vars']['credentials']['password']; + $this->assertEquals(Raven_Processor_SanitizeDataProcessor::STRING_MASK, $passwordValue1); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php b/Vendor/sentry/test/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php new file mode 100644 index 0000000..7e01dfa --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/Processor/SanitizeHttpHeadersProcessorTest.php @@ -0,0 +1,83 @@ +getMockBuilder('\Raven_Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->processor = new Raven_Processor_SanitizeHttpHeadersProcessor($client); + $this->processor->setProcessorOptions(array( + 'sanitize_http_headers' => array('User-Defined-Header'), + )); + } + + /** + * @dataProvider processDataProvider + */ + public function testProcess($inputData, $expectedData) + { + $this->processor->process($inputData); + + $this->assertArraySubset($expectedData, $inputData); + } + + public function processDataProvider() + { + return array( + array( + array( + 'request' => array( + 'headers' => array( + 'Authorization' => 'foo', + 'AnotherHeader' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'headers' => array( + 'Authorization' => Raven_Processor::STRING_MASK, + 'AnotherHeader' => 'bar', + ), + ), + ), + ), + array( + array( + 'request' => array( + 'headers' => array( + 'User-Defined-Header' => 'foo', + 'AnotherHeader' => 'bar', + ), + ), + ), + array( + 'request' => array( + 'headers' => array( + 'User-Defined-Header' => Raven_Processor::STRING_MASK, + 'AnotherHeader' => 'bar', + ), + ), + ), + ), + ); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php b/Vendor/sentry/test/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php new file mode 100644 index 0000000..874a518 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/Processor/SanitizeStacktraceProcessorTest.php @@ -0,0 +1,113 @@ +client = $this->getMockBuilder('Raven_Client') + ->setMethods(array_diff($this->getClassMethods('Raven_Client'), array('captureException', 'capture', 'get_default_data', 'get_http_data', 'get_user_data', 'get_extra_data'))) + ->getMock(); + + $this->client->store_errors_for_bulk_send = true; + + $this->processor = new Raven_Processor_SanitizeStacktraceProcessor($this->client); + } + + public function testProcess() + { + try { + throw new \Exception(); + } catch (\Exception $exception) { + $this->client->captureException($exception); + } + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayHasKey('pre_context', $frame); + $this->assertArrayHasKey('context_line', $frame); + $this->assertArrayHasKey('post_context', $frame); + } + } + + $this->processor->process($this->client->_pending_events[0]); + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayNotHasKey('pre_context', $frame); + $this->assertArrayNotHasKey('context_line', $frame); + $this->assertArrayNotHasKey('post_context', $frame); + } + } + } + + public function testProcessWithPreviousException() + { + try { + try { + throw new \Exception('foo'); + } catch (\Exception $exception) { + throw new \Exception('bar', 0, $exception); + } + } catch (\Exception $exception) { + $this->client->captureException($exception); + } + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayHasKey('pre_context', $frame); + $this->assertArrayHasKey('context_line', $frame); + $this->assertArrayHasKey('post_context', $frame); + } + } + + $this->processor->process($this->client->_pending_events[0]); + + foreach ($this->client->_pending_events[0]['exception']['values'] as $exceptionValue) { + foreach ($exceptionValue['stacktrace']['frames'] as $frame) { + $this->assertArrayNotHasKey('pre_context', $frame); + $this->assertArrayNotHasKey('context_line', $frame); + $this->assertArrayNotHasKey('post_context', $frame); + } + } + } + + /** + * Gets all the public and abstracts methods of a given class. + * + * @param string $className The FCQN of the class + * + * @return array + */ + private function getClassMethods($className) + { + $class = new ReflectionClass($className); + $methods = array(); + + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); + } + } + + return $methods; + } +} diff --git a/Vendor/sentry/test/Raven/Tests/ReprSerializerTest.php b/Vendor/sentry/test/Raven/Tests/ReprSerializerTest.php new file mode 100644 index 0000000..ce251b0 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/ReprSerializerTest.php @@ -0,0 +1,112 @@ +serialize($input); + $this->assertEquals(array('1', '2', '3'), $result); + } + + public function testObjectsAreStrings() + { + $serializer = new Raven_ReprSerializer(); + $input = new Raven_StacktraceTestObject(); + $result = $serializer->serialize($input); + $this->assertEquals('Object Raven_StacktraceTestObject', $result); + } + + public function testIntsAreInts() + { + $serializer = new Raven_ReprSerializer(); + $input = 1; + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertEquals(1, $result); + } + + public function testFloats() + { + $serializer = new Raven_ReprSerializer(); + $input = 1.5; + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertEquals('1.5', $result); + } + + public function testBooleans() + { + $serializer = new Raven_ReprSerializer(); + $input = true; + $result = $serializer->serialize($input); + $this->assertEquals('true', $result); + + $input = false; + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertEquals('false', $result); + } + + public function testNull() + { + $serializer = new Raven_ReprSerializer(); + $input = null; + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertEquals('null', $result); + } + + public function testRecursionMaxDepth() + { + $serializer = new Raven_ReprSerializer(); + $input = array(); + $input[] = &$input; + $result = $serializer->serialize($input, 3); + $this->assertEquals(array(array(array('Array of length 1'))), $result); + } + + /** + * @covers Raven_ReprSerializer::serializeValue + */ + public function testSerializeValueResource() + { + $serializer = new Raven_ReprSerializer(); + $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + $fo = fopen($filename, 'wb'); + + $result = $serializer->serialize($fo); + $this->assertInternalType('string', $result); + $this->assertEquals('Resource stream', $result); + } + + /** + * @covers Raven_ReprSerializer::serializeValue + */ + public function testSerializeRoundedFloat() + { + $serializer = new Raven_ReprSerializer(); + + $result = $serializer->serialize((double)1); + $this->assertInternalType('string', $result); + $this->assertEquals('1.0', $result); + + $result = $serializer->serialize((double)floor(5 / 2)); + $this->assertInternalType('string', $result); + $this->assertEquals('2.0', $result); + + $result = $serializer->serialize((double)floor(12345.678901234)); + $this->assertInternalType('string', $result); + $this->assertEquals('12345.0', $result); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/SerializerTest.php b/Vendor/sentry/test/Raven/Tests/SerializerTest.php new file mode 100644 index 0000000..8e5e6a8 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/SerializerTest.php @@ -0,0 +1,147 @@ +serialize($input); + $this->assertEquals(array('1', '2', '3'), $result); + } + + public function testStdClassAreArrays() + { + $serializer = new Raven_Serializer(); + $input = new stdClass(); + $input->foo = 'BAR'; + $result = $serializer->serialize($input); + $this->assertEquals(array('foo' => 'BAR'), $result); + } + + public function testObjectsAreStrings() + { + $serializer = new Raven_Serializer(); + $input = new Raven_SerializerTestObject(); + $result = $serializer->serialize($input); + $this->assertEquals('Object Raven_SerializerTestObject', $result); + } + + public function testIntsAreInts() + { + $serializer = new Raven_Serializer(); + $input = 1; + $result = $serializer->serialize($input); + $this->assertInternalType('integer', $result); + $this->assertEquals(1, $result); + } + + public function testFloats() + { + $serializer = new Raven_Serializer(); + $input = 1.5; + $result = $serializer->serialize($input); + $this->assertInternalType('double', $result); + $this->assertEquals(1.5, $result); + } + + public function testBooleans() + { + $serializer = new Raven_Serializer(); + $input = true; + $result = $serializer->serialize($input); + $this->assertTrue($result); + + $input = false; + $result = $serializer->serialize($input); + $this->assertFalse($result); + } + + public function testNull() + { + $serializer = new Raven_Serializer(); + $input = null; + $result = $serializer->serialize($input); + $this->assertNull($result); + } + + public function testRecursionMaxDepth() + { + $serializer = new Raven_Serializer(); + $input = array(); + $input[] = &$input; + $result = $serializer->serialize($input, 3); + $this->assertEquals(array(array(array('Array of length 1'))), $result); + } + + public function testObjectInArray() + { + $serializer = new Raven_Serializer(); + $input = array('foo' => new Raven_Serializer()); + $result = $serializer->serialize($input); + $this->assertEquals(array('foo' => 'Object Raven_Serializer'), $result); + } + + /** + * @covers Raven_Serializer::serializeString + */ + public function testBrokenEncoding() + { + $serializer = new Raven_Serializer(); + foreach (array('7efbce4384', 'b782b5d8e5', '9dde8d1427', '8fd4c373ca', '9b8e84cb90') as $key) { + $input = pack('H*', $key); + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + if (function_exists('mb_detect_encoding')) { + $this->assertContains(mb_detect_encoding($result), array('ASCII', 'UTF-8')); + } + } + } + + /** + * @covers Raven_Serializer::serializeString + */ + public function testLongString() + { + $serializer = new Raven_Serializer(); + for ($i = 0; $i < 100; $i++) { + foreach (array(100, 1000, 1010, 1024, 1050, 1100, 10000) as $length) { + $input = ''; + for ($i = 0; $i < $length; $i++) { + $input .= chr(mt_rand(0, 255)); + } + $result = $serializer->serialize($input); + $this->assertInternalType('string', $result); + $this->assertLessThanOrEqual(1024, strlen($result)); + } + } + } + + /** + * @covers Raven_Serializer::serializeValue + */ + public function testSerializeValueResource() + { + $serializer = new Raven_Serializer(); + $filename = tempnam(sys_get_temp_dir(), 'sentry_test_'); + $fo = fopen($filename, 'wb'); + + $result = $serializer->serialize($fo); + $this->assertInternalType('string', $result); + $this->assertEquals('Resource stream', $result); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/StacktraceTest.php b/Vendor/sentry/test/Raven/Tests/StacktraceTest.php new file mode 100644 index 0000000..5bb18d2 --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/StacktraceTest.php @@ -0,0 +1,267 @@ + 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); + + if (isset($stack[0]['function']) and ($stack[0]['function'] == 'call_user_func')) { + $offset = 2; + } else { + $offset = 1; + } + $frame = $stack[$offset]; + $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' => 9, + 'function' => 'a_test', + 'args' => array('friend'), + ), + array( + 'file' => dirname(__FILE__).'/resources/b.php', + 'line' => 2, + 'args' => array( + dirname(__FILE__).'/resources/a.php', + ), + 'function' => 'include_once', + ) + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true); + + $frame = $frames[0]; + $this->assertEquals(2, $frame['lineno']); + $this->assertNull($frame['function']); + $this->assertEquals("include_once 'a.php';", $frame['context_line']); + $this->assertFalse(isset($frame['vars'])); + $frame = $frames[1]; + $this->assertEquals(9, $frame['lineno']); + $this->assertEquals('include_once', $frame['function']); + $this->assertEquals('a_test($foo);', $frame['context_line']); + $this->assertEquals(dirname(__FILE__) . '/resources/a.php', $frame['vars']['param1']); + } + + public function testDoesNotModifyCaptureVars() + { + + // PHP's errcontext as passed to the error handler contains REFERENCES to any vars that were in the global scope. + // Modification of these would be really bad, since if control is returned (non-fatal error) we'll have altered the state of things! + $originalFoo = 'bloopblarp'; + $newFoo = $originalFoo; + $nestedArray = array( + 'key' => 'xxxxxxxxxx', + ); + + $frame = array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 9, + "args"=> array( + &$newFoo, + &$nestedArray, + ), + "function" => "a_test", + ); + + $result = Raven_Stacktrace::get_frame_context($frame, 5); + + // Check we haven't modified our vars. + $this->assertEquals($originalFoo, 'bloopblarp'); + $this->assertEquals($nestedArray['key'], 'xxxxxxxxxx'); + + // Check that we did truncate the variable in our output + $this->assertEquals($result['param1'], 'bloop'); + $this->assertEquals($result['param2']['key'], 'xxxxx'); + } + + public function testDoesFixFrameInfo() + { + if (isset($_ENV['HHVM']) and ($_ENV['HHVM'] == 1)) { + $this->markTestSkipped('HHVM stacktrace behaviour'); + return; + } + + /** + * 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, -6); + $skip_call_user_func_fix = false; + if (version_compare(PHP_VERSION, '7.0', '>=')) { + $skip_call_user_func_fix = true; + foreach ($frames as &$frame) { + if (isset($frame['function']) and ($frame['function'] == 'call_user_func')) { + $skip_call_user_func_fix = false; + break; + } + } + unset($frame); + } + + if ($skip_call_user_func_fix) { + $frame = $frames[3]; + $this->assertEquals('raven_test_create_stacktrace', $frame['function']); + $frame = $frames[4]; + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[5]; + $this->assertEquals('raven_test_recurse', $frame['function']); + } else { + $frame = $frames[0]; + $this->assertEquals('raven_test_create_stacktrace', $frame['function']); + $frame = $frames[1]; + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[2]; + $this->assertEquals('call_user_func', $frame['function']); + $frame = $frames[3]; + $this->assertEquals('raven_test_recurse', $frame['function']); + $frame = $frames[4]; + $this->assertEquals('call_user_func', $frame['function']); + $frame = $frames[5]; + $this->assertEquals('raven_test_recurse', $frame['function']); + } + } + + public function testInApp() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + ), + array( + "file" => dirname(__FILE__) . "/resources/b.php", + "line" => 3, + "function" => "include_once", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, 0, null, dirname(__FILE__)); + + $this->assertEquals($frames[0]['in_app'], true); + $this->assertEquals($frames[1]['in_app'], true); + } + + public function testInAppWithAnonymous() + { + $stack = array( + array( + "function" => "[Anonymous function]", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, 0, null, dirname(__FILE__)); + + $this->assertEquals($frames[0]['in_app'], false); + } + + public function testInAppWithExclusion() + { + $stack = array( + array( + "file" => dirname(__FILE__) . '/resources/foo/a.php', + "line" => 11, + "function" => "a_test", + ), + array( + "file" => dirname(__FILE__) . '/resources/bar/b.php', + "line" => 3, + "function" => "include_once", + ), + array( + "file" => dirname(__FILE__) . '/resources/foo/c.php', + "line" => 3, + "function" => "include_once", + ) + ); + + $frames = Raven_Stacktrace::get_stack_info( + $stack, true, null, 0, null, dirname(__FILE__) . '/', + array(dirname(__FILE__) . '/resources/bar/', dirname(__FILE__) . '/resources/foo/c.php')); + + // stack gets reversed + $this->assertEquals($frames[0]['in_app'], false); + $this->assertEquals($frames[1]['in_app'], false); + $this->assertEquals($frames[2]['in_app'], true); + } + + public function testBasePath() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack, true, null, 0, array(dirname(__FILE__) . '/')); + + $this->assertEquals($frames[0]['filename'], 'resources/a.php'); + } + + public function testNoBasePath() + { + $stack = array( + array( + "file" => dirname(__FILE__) . "/resources/a.php", + "line" => 11, + "function" => "a_test", + ), + ); + + $frames = Raven_Stacktrace::get_stack_info($stack); + $this->assertEquals($frames[0]['filename'], dirname(__FILE__) . '/resources/a.php'); + } + + public function testWithEvaldCode() + { + try { + eval("throw new Exception('foobar');"); + } catch (Exception $ex) { + $trace = $ex->getTrace(); + $frames = Raven_Stacktrace::get_stack_info($trace); + } + /** + * @var array $frames + */ + $this->assertEquals($frames[count($frames) -1]['filename'], __FILE__); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/TransactionStackTest.php b/Vendor/sentry/test/Raven/Tests/TransactionStackTest.php new file mode 100644 index 0000000..694539f --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/TransactionStackTest.php @@ -0,0 +1,35 @@ +push('hello'); + /** @noinspection PhpVoidFunctionResultUsedInspection */ + /** @noinspection PhpUnusedLocalVariableInspection */ + $foo = $stack->push('foo'); + $stack->push('bar'); + $stack->push('world'); + $this->assertEquals($stack->peek(), 'world'); + $this->assertEquals($stack->pop(), 'world'); + $this->assertEquals($stack->pop('foo'), 'foo'); + $this->assertEquals($stack->peek(), 'hello'); + $this->assertEquals($stack->pop(), 'hello'); + $this->assertEquals($stack->peek(), null); + + $stack->clear(); + $this->assertInternalType('array', $stack->stack); + $this->assertEquals(0, count($stack->stack)); + } +} diff --git a/Vendor/sentry/test/Raven/Tests/UtilTest.php b/Vendor/sentry/test/Raven/Tests/UtilTest.php new file mode 100644 index 0000000..6e1a5fa --- /dev/null +++ b/Vendor/sentry/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/Vendor/sentry/test/Raven/Tests/resources/a.php b/Vendor/sentry/test/Raven/Tests/resources/a.php new file mode 100644 index 0000000..1d8ed2e --- /dev/null +++ b/Vendor/sentry/test/Raven/Tests/resources/a.php @@ -0,0 +1,9 @@ +captureException(new \Exception()); diff --git a/Vendor/sentry/test/bootstrap.php b/Vendor/sentry/test/bootstrap.php new file mode 100644 index 0000000..1544bc3 --- /dev/null +++ b/Vendor/sentry/test/bootstrap.php @@ -0,0 +1,17 @@ +