diff --git a/.github/workflows/license-audit.yml b/.github/workflows/license-audit.yml new file mode 100644 index 00000000..3d2768a2 --- /dev/null +++ b/.github/workflows/license-audit.yml @@ -0,0 +1,31 @@ +name: Audit bugsnag-php dependency licenses + +on: [push, pull_request] + +jobs: + license-audit: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + coverage: none + extensions: intl, mbstring + + - name: Fetch decisions.yml + run: curl https://raw.githubusercontent.com/bugsnag/license-audit/master/config/decision_files/global.yml -o decisions.yml + + - name: Install composer dependencies + run: composer install --no-dev + + - name: Run License Finder + # for some reason license finder doesn't run without a login shell (-l) + run: > + docker run -v $PWD:/scan licensefinder/license_finder /bin/bash -lc " + cd /scan && + license_finder --decisions-file decisions.yml --composer-check-require-only=true + " diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 6ef0ec61..e7b2923f 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -8,17 +8,20 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ 5.5, 5.6, 7.0, 7.1, 7.2, 7.3, 7.4 ] - guzzle-version: [ '^5.3', '^6.0' ] + php-version: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] + guzzle-version: ['^5.3', '^6.0'] include: - - php-version: 7.2 + - php-version: '7.2' guzzle-version: '^7.0' - - php-version: 7.3 + - php-version: '7.3' guzzle-version: '^7.0' - - php-version: 7.4 + - php-version: '7.4' guzzle-version: '^7.0' - - php-version: 8.0 + - php-version: '8.0' guzzle-version: '^7.0' + - php-version: '8.1' + guzzle-version: '^7.0' + composer-flags: '--ignore-platform-req php' steps: - uses: actions/checkout@v2 @@ -29,6 +32,14 @@ jobs: php-version: ${{ matrix.php-version }} coverage: none extensions: intl, mbstring + # by default setup-php uses a production php.ini so force development values + ini-values: >- + zend.exception_ignore_args=Off, + zend.exception_string_param_max_len=15, + error_reporting=-1, + display_errors=On, + display_startup_errors=On, + zend.assertions=1 - run: composer validate diff --git a/.gitignore b/.gitignore index 89cf333a..3cece17f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor /composer.phar /.idea .phpunit.result.cache +decisions.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9bb53a..86a5d880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Changelog ========= +## 3.26.1 (2021-09-09) + +### Fixes + +* Avoid JSON encoding event payloads more than once, where possible + [#628](https://github.com/bugsnag/bugsnag-php/pull/628) +* Add the `ReturnTypeWillChange` to `Bugsnag\Breadcrumbs\Recorder` to avoid a deprecation in PHP 8.1 + [#630](https://github.com/bugsnag/bugsnag-php/pull/630) + ## 3.26.0 (2021-02-10) ### Enhancements diff --git a/src/Breadcrumbs/Recorder.php b/src/Breadcrumbs/Recorder.php index 8a6bbf88..9c4bc511 100644 --- a/src/Breadcrumbs/Recorder.php +++ b/src/Breadcrumbs/Recorder.php @@ -85,6 +85,7 @@ public function clear() * * @return int */ + #[\ReturnTypeWillChange] public function count() { return count($this->breadcrumbs); @@ -95,6 +96,7 @@ public function count() * * @return \Bugsnag\Breadcrumbs\Breadcrumb */ + #[\ReturnTypeWillChange] public function current() { return $this->breadcrumbs[($this->head + $this->position) % static::MAX_ITEMS]; @@ -105,6 +107,7 @@ public function current() * * @return int */ + #[\ReturnTypeWillChange] public function key() { return $this->position; @@ -115,6 +118,7 @@ public function key() * * @return void */ + #[\ReturnTypeWillChange] public function next() { $this->position++; @@ -125,6 +129,7 @@ public function next() * * @return void */ + #[\ReturnTypeWillChange] public function rewind() { $this->position = 0; @@ -135,6 +140,7 @@ public function rewind() * * @return int */ + #[\ReturnTypeWillChange] public function valid() { return $this->position < $this->count(); diff --git a/src/Configuration.php b/src/Configuration.php index c3856edc..b2bb6949 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -84,7 +84,7 @@ class Configuration */ protected $notifier = [ 'name' => 'Bugsnag PHP (Official)', - 'version' => '3.26.0', + 'version' => '3.26.1', 'url' => 'https://bugsnag.com', ]; diff --git a/src/HttpClient.php b/src/HttpClient.php index b6ee032a..49c4ae96 100644 --- a/src/HttpClient.php +++ b/src/HttpClient.php @@ -255,6 +255,7 @@ protected function getHeaders($version = self::NOTIFY_PAYLOAD_VERSION) 'Bugsnag-Api-Key' => $this->config->getApiKey(), 'Bugsnag-Sent-At' => Date::now(), 'Bugsnag-Payload-Version' => $version, + 'Content-Type' => 'application/json', ]; } @@ -324,7 +325,7 @@ protected function deliverEvents($uri, array $data) $this->post( $uri, [ - 'json' => $normalized, + 'body' => $normalized, 'headers' => $this->getHeaders(self::NOTIFY_PAYLOAD_VERSION), ] ); @@ -340,23 +341,25 @@ protected function deliverEvents($uri, array $data) * * @throws RuntimeException * - * @return array + * @return string the JSON encoded data after normalization */ protected function normalize(array $data) { $body = json_encode($data); - if ($this->length($body) > static::MAX_SIZE) { - unset($data['events'][0]['metaData']); + if ($this->length($body) <= static::MAX_SIZE) { + return $body; } + unset($data['events'][0]['metaData']); + $body = json_encode($data); if ($this->length($body) > static::MAX_SIZE) { throw new RuntimeException('Payload too large'); } - return $data; + return $body; } /** diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 441e59ad..3ebb5b43 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -70,8 +70,10 @@ public function testManualErrorNotificationWithSeverity() $this->expectGuzzlePostWithCallback( $this->client->getNotifyEndpoint(), function ($options) { - $this->assertTrue(isset($options['json']['events'][0]['severity'])); - $this->assertSame('info', $options['json']['events'][0]['severity']); + $payload = $this->getPayloadFromGuzzleOptions($options); + + $this->assertTrue(isset($payload['events'][0]['severity'])); + $this->assertSame('info', $payload['events'][0]['severity']); return true; } @@ -97,8 +99,10 @@ public function testManualExceptionNotificationWithSeverity() $this->expectGuzzlePostWithCallback( $this->client->getNotifyEndpoint(), function ($options) { - $this->assertTrue(isset($options['json']['events'][0]['severity'])); - $this->assertSame('info', $options['json']['events'][0]['severity']); + $payload = $this->getPayloadFromGuzzleOptions($options); + + $this->assertTrue(isset($payload['events'][0]['severity'])); + $this->assertSame('info', $payload['events'][0]['severity']); return true; } diff --git a/tests/HttpClientTest.php b/tests/HttpClientTest.php index 2763ac8b..7bc4271b 100644 --- a/tests/HttpClientTest.php +++ b/tests/HttpClientTest.php @@ -59,13 +59,15 @@ private function setExpectedGuzzleParameters($expectation, $callback) public function testHttpClient() { $verifyGuzzleParameters = function ($options) { - Assert::isType('array', $options); - Assert::isType('array', $options['json']['notifier']); - Assert::isType('array', $options['json']['events']); - $this->assertSame([], $options['json']['events'][0]['user']); - $this->assertSame(['foo' => 'bar'], $options['json']['events'][0]['metaData']); - $this->assertSame('6015a72ff14038114c3d12623dfb018f', $options['json']['apiKey']); - $this->assertSame('4.0', $options['json']['events'][0]['payloadVersion']); + $payload = $this->getPayloadFromGuzzleOptions($options); + + Assert::isType('array', $payload); + Assert::isType('array', $payload['notifier']); + Assert::isType('array', $payload['events']); + $this->assertSame([], $payload['events'][0]['user']); + $this->assertSame(['foo' => 'bar'], $payload['events'][0]['metaData']); + $this->assertSame('6015a72ff14038114c3d12623dfb018f', $payload['apiKey']); + $this->assertSame('4.0', $payload['events'][0]['payloadVersion']); $headers = $options['headers']; @@ -88,13 +90,15 @@ public function testHttpClient() public function testHttpClientMultipleSend() { $verifyGuzzleParameters = function ($options) { - Assert::isType('array', $options); - Assert::isType('array', $options['json']['notifier']); - Assert::isType('array', $options['json']['events']); - $this->assertSame([], $options['json']['events'][0]['user']); - $this->assertSame(['foo' => 'bar'], $options['json']['events'][0]['metaData']); - $this->assertSame('6015a72ff14038114c3d12623dfb018f', $options['json']['apiKey']); - $this->assertSame('4.0', $options['json']['events'][0]['payloadVersion']); + $payload = $this->getPayloadFromGuzzleOptions($options); + + Assert::isType('array', $payload); + Assert::isType('array', $payload['notifier']); + Assert::isType('array', $payload['events']); + $this->assertSame([], $payload['events'][0]['user']); + $this->assertSame(['foo' => 'bar'], $payload['events'][0]['metaData']); + $this->assertSame('6015a72ff14038114c3d12623dfb018f', $payload['apiKey']); + $this->assertSame('4.0', $payload['events'][0]['payloadVersion']); $headers = $options['headers']; @@ -118,13 +122,15 @@ public function testHttpClientMultipleSend() public function testMassiveMetaDataHttpClient() { $verifyGuzzleParameters = function ($options) { - Assert::isType('array', $options); - Assert::isType('array', $options['json']['notifier']); - Assert::isType('array', $options['json']['events']); - $this->assertSame([], $options['json']['events'][0]['user']); - $this->assertArrayNotHasKey('metaData', $options['json']['events'][0]); - $this->assertSame('6015a72ff14038114c3d12623dfb018f', $options['json']['apiKey']); - $this->assertSame('4.0', $options['json']['events'][0]['payloadVersion']); + $payload = $this->getPayloadFromGuzzleOptions($options); + + Assert::isType('array', $payload); + Assert::isType('array', $payload['notifier']); + Assert::isType('array', $payload['events']); + $this->assertSame([], $payload['events'][0]['user']); + $this->assertArrayNotHasKey('metaData', $payload['events'][0]); + $this->assertSame('6015a72ff14038114c3d12623dfb018f', $payload['apiKey']); + $this->assertSame('4.0', $payload['events'][0]['payloadVersion']); $headers = $options['headers']; @@ -164,13 +170,15 @@ public function testPartialHttpClient() $log->expects($this->once())->with($this->equalTo('Bugsnag Warning: Payload too large')); $verifyGuzzleParameters = function ($options) { - Assert::isType('array', $options); - Assert::isType('array', $options['json']['notifier']); - Assert::isType('array', $options['json']['events']); - $this->assertSame(['foo' => 'bar'], $options['json']['events'][0]['user']); - $this->assertSame([], $options['json']['events'][0]['metaData']); - $this->assertSame('6015a72ff14038114c3d12623dfb018f', $options['json']['apiKey']); - $this->assertSame('4.0', $options['json']['events'][0]['payloadVersion']); + $payload = $this->getPayloadFromGuzzleOptions($options); + + Assert::isType('array', $payload); + Assert::isType('array', $payload['notifier']); + Assert::isType('array', $payload['events']); + $this->assertSame(['foo' => 'bar'], $payload['events'][0]['user']); + $this->assertSame([], $payload['events'][0]['metaData']); + $this->assertSame('6015a72ff14038114c3d12623dfb018f', $payload['apiKey']); + $this->assertSame('4.0', $payload['events'][0]['payloadVersion']); $headers = $options['headers']; $this->assertSame('6015a72ff14038114c3d12623dfb018f', $headers['Bugsnag-Api-Key']); diff --git a/tests/TestCase.php b/tests/TestCase.php index 439200f0..b6639fdf 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -56,4 +56,16 @@ private function phpUnitVersion() return PhpUnitVersion::id(); } + + protected function getPayloadFromGuzzleOptions(array $options) + { + $this->assertArrayHasKey('body', $options); + Assert::isType('string', $options['body']); + + $payload = json_decode($options['body'], true); + + $this->assertSame(JSON_ERROR_NONE, json_last_error(), json_last_error_msg()); + + return $payload; + } } diff --git a/tests/phpt/Utilities/FakeGuzzle.php b/tests/phpt/Utilities/FakeGuzzle.php index 647844b7..5955f881 100644 --- a/tests/phpt/Utilities/FakeGuzzle.php +++ b/tests/phpt/Utilities/FakeGuzzle.php @@ -54,13 +54,19 @@ function reportRequest($method, $uri, $options) $numberOfEvents = 0; $errors = []; - if (isset($options['json']['events'])) { - $numberOfEvents = count($options['json']['events']); + if (isset($options['body'])) { + $payload = json_decode($options['body'], true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new RuntimeException('Unable to decode JSON body: '.json_last_error_msg()); + } + + $numberOfEvents = count($payload['events']); $errors = array_map( function ($event) { return strtok($event['exceptions'][0]['message'], "\n"); }, - $options['json']['events'] + $payload['events'] ); }