Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: composer
directory: /
schedule:
interval: weekly
groups:
composer-minor:
update-types:
- minor
- patch
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
needs: [ install_dependencies ]
strategy:
matrix:
php-versions: [ "7.4", "8.0", "8.1", "8.2", "8.3", "8.4" ]
php-versions: [ "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5" ]

runs-on: ubuntu-latest

Expand Down
2 changes: 1 addition & 1 deletion bump_version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ new_version=${1#v}

echo "New version: $new_version"

if ! [[ "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9](-[a-z]+\.[0-9]+)?$ ]]
if ! [[ "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?$ ]]
Comment thread
adriaanzon marked this conversation as resolved.
then
echo "Invalid version format. Please use semantic versioning (https://semver.org/)."
exit 1
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"phpstan/phpstan": "^1",
"phpstan/phpstan": "^2.2",
"mockery/mockery": "^1.5",
"php-cs-fixer/shim": "^3.87",
"psr/http-client": "^1.0",
Expand Down
6 changes: 3 additions & 3 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/
class Connection
{
public const VERSION = '3.0.0';
public const VERSION = '3.0.1';

public const BASE_URL = 'https://app.sendy.nl';

Expand Down Expand Up @@ -63,8 +63,8 @@ class Connection
/** @var mixed */
private $state = null;

/** @var callable($this) */
private $tokenUpdateCallback;
/** @var (callable(self): void)|null */
private $tokenUpdateCallback = null;

private bool $oauthClient = false;

Expand Down
19 changes: 16 additions & 3 deletions src/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,24 @@ final class Response
public function __construct(int $statusCode, array $headers, string $body)
{
$this->statusCode = $statusCode;
$this->headers = array_map(
fn($value) => is_array($value) ? $value : [$value],
$this->headers = $this->normalizeHeaders($headers);
$this->body = $body;
}

/**
* Lowercase the header names and strip CR and LF from the values, so
* consumers can safely emit headers in their own HTTP responses without
* risking header injection.
*
* @param array<string, list<string>|string> $headers
* @return array<string, list<string>>
*/
private function normalizeHeaders(array $headers): array
{
return array_map(
fn($value) => str_replace(["\r", "\n"], '', is_array($value) ? $value : [$value]),
array_change_key_case($headers, CASE_LOWER),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
$this->body = $body;
}

public function getStatusCode(): int
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Transport/MockTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class MockTransport implements TransportInterface
{
private ?Response $response;
private Response $response;

/**
* @var list<Request>
Expand Down
40 changes: 30 additions & 10 deletions tests/ConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Sendy\Api\ApiException;
use Sendy\Api\Connection;
use Sendy\Api\Exceptions\ClientException;
use Sendy\Api\Exceptions\SendyException;
use Sendy\Api\Http\Request;
use Sendy\Api\Http\Response;
use Sendy\Api\Http\Transport\MockTransport;
Expand All @@ -16,28 +15,33 @@

class ConnectionTest extends TestCase
{
public function testVersionUsesSemverFormat(): void
{
$this->assertMatchesRegularExpression('/^\d+\.\d+\.\d+(-[a-z]+\.\d+)?$/', Connection::VERSION);
}

public function testUserAgentIsSet(): void
{
$phpVersion = phpversion();
$curlVersion = curl_version()['version'];

$connection = $this->createConnection();
$this->assertEquals(
"SendySDK/3.0.0 PHP/{$phpVersion} curl/{$curlVersion}",
"SendySDK/" . Connection::VERSION . " PHP/{$phpVersion} curl/{$curlVersion}",
$connection->createRequest('GET', '/')->getHeaders()['user-agent'],
);

$connection = $this->createConnection();
$connection->setUserAgentAppendix('WooCommerce/6.2');
$this->assertEquals(
"SendySDK/3.0.0 PHP/{$phpVersion} curl/{$curlVersion} WooCommerce/6.2",
"SendySDK/" . Connection::VERSION . " PHP/{$phpVersion} curl/{$curlVersion} WooCommerce/6.2",
$connection->createRequest('GET', '/')->getHeaders()['user-agent'],
);

$connection = $this->createConnection();
$connection->setOauthClient(true);
$this->assertEquals(
"SendySDK/3.0.0 PHP/{$phpVersion} OAuth/2.0 curl/{$curlVersion}",
"SendySDK/" . Connection::VERSION . " PHP/{$phpVersion} OAuth/2.0 curl/{$curlVersion}",
$connection->createRequest('GET', '/')->getHeaders()['user-agent'],
);
}
Expand Down Expand Up @@ -133,6 +137,25 @@ public function testParseResponseExtractsMeta(): void
$this->assertInstanceOf(Meta::class, $connection->meta);
}

public function testParseResponseExposesOnlySendyHeadersWithSanitizedValues(): void
{
$connection = new Connection();

$response = new Response(200, [
'content-type' => ['application/json'],
'X-Sendy-Token' => ["secret\r\nx-injected: value"],
'x-sendy-shipment-id' => ['123'],
'x-ratelimit-remaining' => ['59'],
], json_encode(['data' => []]));

$connection->parseResponse($response, new Request('GET', '/foo'));

$this->assertEquals([
'x-sendy-token' => ['secretx-injected: value'],
'x-sendy-shipment-id' => ['123'],
], $connection->sendyHeaders);
}

public function testParseResponseUnwrapsData(): void
{
$connection = new Connection();
Expand Down Expand Up @@ -234,13 +257,10 @@ public function testRevokedRefreshTokenIsHandled(): void
$connection->setRefreshToken('RefreshToken');
$connection->setTokenExpires(time() + 5);

try {
$connection->checkOrAcquireAccessToken();
} catch (SendyException $exception) {
$this->fail($exception->getMessage());
}
$connection->checkOrAcquireAccessToken();

$this->assertTrue(true);
$this->assertSame('accessToken', $connection->getAccessToken());
$this->assertSame('RefreshToken', $connection->getRefreshToken());
}

public function testUnexpectedExceptionWhenRefreshingTokensAreHandled(): void
Expand Down
Loading