Compare commits

...
Author SHA1 Message Date
Hemachandar b6cffcc5b5 simplify 2026-02-18 21:29:57 +05:30
Hemachandar 6d3bcf91b6 Merge branch '1.8.x' into ser-1102 2026-02-18 21:15:16 +05:30
Hemachandar 7a89884a0c more lint 2026-02-16 14:32:03 +05:30
Hemachandar 57ddf0ac76 lint 2026-02-16 14:02:38 +05:30
Hemachandar 6e1b7f1a47 Merge branch '1.8.x' into ser-1102 2026-02-16 13:54:31 +05:30
Hemachandar 6c5e73de3a use Filter 2026-02-16 13:53:33 +05:30
Hemachandar 90e69b011f ut 2026-02-04 18:21:24 +05:30
Hemachandar 7cc97d4210 fix test 2026-02-04 18:05:28 +05:30
Hemachandar 59a4f1ef12 tiny 2026-02-04 17:58:45 +05:30
Hemachandar 979cb087e1 lint 2026-02-04 17:42:13 +05:30
HemachandarandGitHub 6f9bb1487c Merge branch '1.8.x' into ser-1102 2026-02-04 17:37:33 +05:30
Hemachandar 4114d9dcb1 Move to transformation adapter 2026-02-04 17:36:59 +05:30
HemachandarandClaude Opus 4.5 90c96e9318 refactor: use Text constants for branch name sanitization
Replace regex with explicit character validation using Utopia Text
constants for better readability and maintainability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:11:11 +05:30
HemachandarandGitHub 4228321d76 Merge branch '1.8.x' into ser-1102 2026-02-04 15:50:43 +05:30
HemachandarandClaude Opus 4.5 3b96abf02a fix: sanitize branch names for valid domain generation
Branch names containing invalid domain characters (like '/') were being
used directly when creating VCS preview domains, resulting in invalid
domains like 'branch-abc/test.appwrite.network'. This adds a Domain
helper class that sanitizes branch names by replacing invalid characters
with hyphens before generating domains.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:49:40 +05:30
6 changed files with 232 additions and 22 deletions
+7 -7
View File
@@ -2,6 +2,7 @@
use Appwrite\Event\Build;
use Appwrite\Extend\Exception;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -316,13 +317,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
// VCS branch preview
if (!empty($providerBranch)) {
$branchPrefix = substr($providerBranch, 0, 16);
if (strlen($providerBranch) > 16) {
$remainingChars = substr($providerBranch, 16);
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
}
$resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7);
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$domain = (new BranchDomainFilter())->apply([
'branch' => $providerBranch,
'resourceId' => $resource->getId(),
'projectId' => $project->getId(),
'sitesDomain' => $sitesDomain,
]);
$ruleId = md5($domain);
try {
$authorization->skip(
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace Appwrite\Filter;
use Utopia\Validator\Text;
class BranchDomain implements Filter
{
/**
* Maximum length for branch prefix in domain name
*/
public const BRANCH_PREFIX_MAX_LENGTH = 16;
/**
* Length of hash suffix when branch name exceeds max length
*/
public const HASH_SUFFIX_LENGTH = 7;
/**
* Pre-process branch name to a valid domain name.
*
* Input should be an array with:
* - 'branch' (string): The branch name
* - 'resourceId' (string): The resource ID (site or function)
* - 'projectId' (string): The project ID
* - 'sitesDomain' (string): The base sites domain
*/
public function apply(mixed $input): mixed
{
$branch = $input['branch'] ?? '';
$resourceId = $input['resourceId'] ?? '';
$projectId = $input['projectId'] ?? '';
$sitesDomain = $input['sitesDomain'] ?? '';
$branchPrefix = $this->generateBranchPrefix($branch);
$resourceProjectHash = substr(hash('sha256', $resourceId . $projectId), 0, self::HASH_SUFFIX_LENGTH);
$domain = \strtolower("branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}");
return $domain;
}
/**
* Generate a branch prefix for domain name from a branch name.
* Takes up to 16 characters, sanitizes them for domain use,
* and appends a hash suffix if the branch name is longer than 16 characters.
*
* @param string $branch The branch name
* @return string The branch prefix for domain name
*/
private function generateBranchPrefix(string $branch): string
{
$branchPrefix = substr($branch, 0, self::BRANCH_PREFIX_MAX_LENGTH);
$branchPrefix = $this->sanitizeBranchName($branchPrefix);
if (strlen($branch) > self::BRANCH_PREFIX_MAX_LENGTH) {
$remainingChars = substr($branch, self::BRANCH_PREFIX_MAX_LENGTH);
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, self::HASH_SUFFIX_LENGTH);
}
return $branchPrefix;
}
/**
* Sanitize a branch name for use in a domain name.
* Replaces any characters that are not alphanumeric or hyphens with hyphens,
* and removes leading/trailing hyphens.
*
* @param string $branch The branch name to sanitize
* @return string The sanitized branch name
*/
private function sanitizeBranchName(string $branch): string
{
$allowedChars = array_merge(
Text::NUMBERS,
Text::ALPHABET_UPPER,
Text::ALPHABET_LOWER,
['-']
);
$allowedCharsFlip = array_flip($allowedChars);
$sanitized = '';
for ($i = 0; $i < \strlen($branch); $i++) {
$char = $branch[$i];
if (isset($allowedCharsFlip[$char])) {
$sanitized .= $char;
} else {
// Prevents two -- or more in a row
if (strlen($sanitized) > 0 && $sanitized[strlen($sanitized) - 1] !== '-') {
$sanitized .= '-';
}
}
}
return trim($sanitized, '-');
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace Appwrite\Filter;
interface Filter
{
public function apply(mixed $input): mixed;
}
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Compute;
use Appwrite\Event\Build;
use Appwrite\Extend\Exception;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Validator\Specification as SpecificationValidator;
use Utopia\Config\Config;
@@ -326,13 +327,12 @@ class Base extends Action
// VCS branch preview
if (!empty($providerBranch)) {
$branchPrefix = substr($providerBranch, 0, 16);
if (strlen($providerBranch) > 16) {
$remainingChars = substr($providerBranch, 16);
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
}
$resourceProjectHash = substr(hash('sha256', $site->getId() . $project->getId()), 0, 7);
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$domain = (new BranchDomainFilter())->apply([
'branch' => $providerBranch,
'resourceId' => $site->getId(),
'projectId' => $project->getId(),
'sitesDomain' => $sitesDomain,
]);
$ruleId = md5($domain);
try {
$authorization->skip(
@@ -9,6 +9,7 @@ use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Vcs\Comment;
use Exception;
@@ -1027,14 +1028,12 @@ class Builds extends Action
// VCS branch
$branchName = $deployment->getAttribute('providerBranch');
if (!empty($branchName)) {
$sitesDomain = $platform['sitesDomain'];
$branchPrefix = substr($branchName, 0, 16);
if (strlen($branchName) > 16) {
$remainingChars = substr($branchName, 16);
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
}
$resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7);
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$domain = (new BranchDomainFilter())->apply([
'branch' => $branchName,
'resourceId' => $resource->getId(),
'projectId' => $project->getId(),
'sitesDomain' => $platform['sitesDomain'],
]);
$ruleId = md5($domain);
try {
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace Tests\Unit\Filter;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use PHPUnit\Framework\TestCase;
class BranchDomainTest extends TestCase
{
public function testBranchDomain(): void
{
$filter = new BranchDomainFilter();
// Branch name with slash
$domain = $filter->apply([
'branch' => 'feature/test',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertStringNotContainsString('/', $domain);
$this->assertStringStartsWith('branch-feature-test-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
// Branch domain consistency
$domain2 = $filter->apply([
'branch' => 'feature/test',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertEquals($domain, $domain);
// Different resources should produce different domains
$domain2 = $filter->apply([
'branch' => 'feature/test',
'resourceId' => 'site789',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertNotEquals($domain, $domain2);
// Different projects should produce different domains
$domain2 = $filter->apply([
'branch' => 'feature/test',
'resourceId' => 'site123',
'projectId' => 'proj789',
'sitesDomain' => 'appwrite.network'
]);
$this->assertNotEquals($domain, $domain2);
// Some real-world branch names
$domain = $filter->apply([
'branch' => 'feature/SER-1234',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertStringStartsWith('branch-feature-ser-1234-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
$domain = $filter->apply([
'branch' => 'bugfix/fix-login',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertStringStartsWith('branch-bugfix-fix-login-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
$domain = $filter->apply([
'branch' => 'hotfix/v1.2.3',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertStringStartsWith('branch-hotfix-v1-2-3-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
$domain = $filter->apply([
'branch' => 'release/2024.01',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertStringStartsWith('branch-release-2024-01-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
$domain = $filter->apply([
'branch' => 'user/john/experiment',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertStringStartsWith('branch-user-john-experi-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
$domain = $filter->apply([
'branch' => 'dependabot/npm_and_yarn/lodash-4.17.21',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
]);
$this->assertStringStartsWith('branch-dependabot-npm-a-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
}
}