diff --git a/src/Legacy/IntercomAdmins.php b/src/Legacy/IntercomAdmins.php index fc2b977..2417739 100644 --- a/src/Legacy/IntercomAdmins.php +++ b/src/Legacy/IntercomAdmins.php @@ -43,6 +43,6 @@ public function getAdmin($id, $options = []) */ public function adminPath($id) { - return 'admins/' . $id; + return 'admins/' . self::pathSegment($id); } } diff --git a/src/Legacy/IntercomCompanies.php b/src/Legacy/IntercomCompanies.php index 9729028..9da6088 100644 --- a/src/Legacy/IntercomCompanies.php +++ b/src/Legacy/IntercomCompanies.php @@ -116,7 +116,7 @@ public function getCompanyUsers($id, $options = []) */ public function companyPath($id) { - return 'companies/' . $id; + return 'companies/' . self::pathSegment($id); } /** @@ -125,7 +125,7 @@ public function companyPath($id) */ public function companyUsersPath($id) { - return 'companies/' . $id . '/users'; + return 'companies/' . self::pathSegment($id) . '/users'; } /** @@ -134,7 +134,7 @@ public function companyUsersPath($id) */ public function companyAttachPath(string $contactId) { - return 'contacts/' . $contactId . '/companies'; + return 'contacts/' . self::pathSegment($contactId) . '/companies'; } /** @@ -144,6 +144,6 @@ public function companyAttachPath(string $contactId) */ public function companyDetachPath(string $contactId, string $companyId) { - return 'contacts/' . $contactId . '/companies/' . $companyId; + return 'contacts/' . self::pathSegment($contactId) . '/companies/' . self::pathSegment($companyId); } } diff --git a/src/Legacy/IntercomContacts.php b/src/Legacy/IntercomContacts.php index 1f0de82..8946d7a 100644 --- a/src/Legacy/IntercomContacts.php +++ b/src/Legacy/IntercomContacts.php @@ -128,6 +128,6 @@ public function nextCursor($pages) */ public function contactPath(string $id) { - return 'contacts/' . $id; + return 'contacts/' . self::pathSegment($id); } } diff --git a/src/Legacy/IntercomConversations.php b/src/Legacy/IntercomConversations.php index e5f6de4..a78403a 100644 --- a/src/Legacy/IntercomConversations.php +++ b/src/Legacy/IntercomConversations.php @@ -129,7 +129,7 @@ public function markConversationAsRead($id) */ public function conversationPath($id) { - return 'conversations/' . $id; + return 'conversations/' . self::pathSegment($id); } /** @@ -140,6 +140,6 @@ public function conversationPath($id) */ public function conversationReplyPath($id) { - return 'conversations/' . $id . '/reply'; + return 'conversations/' . self::pathSegment($id) . '/reply'; } } diff --git a/src/Legacy/IntercomLeads.php b/src/Legacy/IntercomLeads.php index 330ccd0..4313552 100644 --- a/src/Legacy/IntercomLeads.php +++ b/src/Legacy/IntercomLeads.php @@ -97,7 +97,7 @@ public function convertLead($options) */ public function leadPath($id) { - return "contacts/" . $id; + return "contacts/" . self::pathSegment($id); } /** diff --git a/src/Legacy/IntercomNotes.php b/src/Legacy/IntercomNotes.php index f3f8dd4..5745ea3 100644 --- a/src/Legacy/IntercomNotes.php +++ b/src/Legacy/IntercomNotes.php @@ -43,6 +43,6 @@ public function getNotes($options) */ public function getNote($id) { - return $this->client->get("notes/" . $id, []); + return $this->client->get("notes/" . self::pathSegment($id), []); } } diff --git a/src/Legacy/IntercomResource.php b/src/Legacy/IntercomResource.php index 5895ebf..ae4ac11 100644 --- a/src/Legacy/IntercomResource.php +++ b/src/Legacy/IntercomResource.php @@ -18,4 +18,20 @@ public function __construct(IntercomClient $client) { $this->client = $client; } + + /** + * Returns $id as a single URL path segment. + * + * @param string|int $id + * @return string + * @throws \InvalidArgumentException if the value is empty, "." or ".." + */ + protected static function pathSegment($id) + { + $id = (string) $id; + if ($id === "" || $id === "." || $id === "..") { + throw new \InvalidArgumentException("Invalid ID for request path."); + } + return rawurlencode($id); + } } diff --git a/src/Legacy/IntercomSegments.php b/src/Legacy/IntercomSegments.php index 458fa16..c4f7d5f 100644 --- a/src/Legacy/IntercomSegments.php +++ b/src/Legacy/IntercomSegments.php @@ -18,7 +18,7 @@ class IntercomSegments extends IntercomResource */ public function getSegment($id, array $options = []) { - return $this->client->get('segments/' . $id, $options); + return $this->client->get('segments/' . self::pathSegment($id), $options); } /** diff --git a/src/Legacy/IntercomTeams.php b/src/Legacy/IntercomTeams.php index ed35060..b945b4b 100644 --- a/src/Legacy/IntercomTeams.php +++ b/src/Legacy/IntercomTeams.php @@ -43,6 +43,6 @@ public function getTeam($id, $options = []) */ public function teamPath($id) { - return 'teams/' . $id; + return 'teams/' . self::pathSegment($id); } } diff --git a/src/Legacy/IntercomUsers.php b/src/Legacy/IntercomUsers.php index 2765967..13f1cb9 100644 --- a/src/Legacy/IntercomUsers.php +++ b/src/Legacy/IntercomUsers.php @@ -124,6 +124,6 @@ public function permanentlyDeleteUser(string $id) */ public function userPath(string $id) { - return 'users/' . $id; + return 'users/' . self::pathSegment($id); } } diff --git a/src/Legacy/IntercomVisitors.php b/src/Legacy/IntercomVisitors.php index d44f493..ced4f91 100644 --- a/src/Legacy/IntercomVisitors.php +++ b/src/Legacy/IntercomVisitors.php @@ -72,6 +72,6 @@ public function convertVisitor($options) */ public function visitorPath($id) { - return "visitors/" . $id; + return "visitors/" . self::pathSegment($id); } } diff --git a/tests/Legacy/IntercomResourcePathTest.php b/tests/Legacy/IntercomResourcePathTest.php new file mode 100644 index 0000000..67bbcd7 --- /dev/null +++ b/tests/Legacy/IntercomResourcePathTest.php @@ -0,0 +1,65 @@ +mockHandler = new MockHandler(); + $httpClient = new Client(['handler' => HandlerStack::create($this->mockHandler)]); + + $this->client = new IntercomClient('test_token'); + $this->client->setHttpClient($httpClient); + } + + public function testEncodesIdInPath(): void + { + $this->mockHandler->append(new Response(200, [], json_encode([]))); + + $this->client->contacts->getContact('a/b?c#d'); + + $uri = $this->mockHandler->getLastRequest()->getUri(); + $this->assertSame('/contacts/a%2Fb%3Fc%23d', $uri->getPath()); + $this->assertSame('', $uri->getQuery()); + $this->assertSame('', $uri->getFragment()); + } + + public function testEncodesEveryIdInCompanyDetachPath(): void + { + $this->assertSame( + 'contacts/a%2Fb/companies/c%3Fd', + $this->client->companies->companyDetachPath('a/b', 'c?d') + ); + } + + public function testKeepsPlainIdUnchanged(): void + { + $this->assertSame('users/abc123', $this->client->users->userPath('abc123')); + } + + /** + * @dataProvider invalidIdProvider + */ + public function testRejectsDotSegmentId(string $id): void + { + $this->expectException(InvalidArgumentException::class); + $this->client->contacts->getContact($id); + } + + public function invalidIdProvider(): array + { + return [[''], ['.'], ['..']]; + } +}