2019-05-09 06:54:39 +00:00
< ? php
2022-05-23 14:54:50 +00:00
require_once __DIR__ . '/../init.php' ;
2019-10-24 17:53:37 +00:00
2024-08-06 19:20:45 +00:00
use Ahc\Jwt\JWT ;
2025-02-10 14:07:01 +00:00
use Ahc\Jwt\JWTException ;
2024-07-04 15:02:34 +00:00
use Appwrite\Auth\Auth ;
2025-02-21 19:50:43 +00:00
use Appwrite\Auth\Key ;
2022-04-13 12:39:31 +00:00
use Appwrite\Event\Certificate ;
2024-02-22 09:40:18 +00:00
use Appwrite\Event\Event ;
2024-10-09 08:34:08 +00:00
use Appwrite\Event\Func ;
2025-01-30 04:53:53 +00:00
use Appwrite\Event\StatsUsage ;
2024-03-06 17:34:21 +00:00
use Appwrite\Extend\Exception as AppwriteException ;
2020-06-11 19:36:10 +00:00
use Appwrite\Network\Validator\Origin ;
2024-11-18 08:52:16 +00:00
use Appwrite\Platform\Appwrite ;
2025-01-17 04:31:39 +00:00
use Appwrite\SDK\Method ;
use Appwrite\SDK\Response as SDKResponse ;
2025-02-11 13:26:49 +00:00
use Appwrite\Transformation\Adapter\Preview ;
use Appwrite\Transformation\Transformation ;
2024-03-06 17:34:21 +00:00
use Appwrite\Utopia\Request ;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16 ;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17 ;
2024-07-17 05:52:17 +00:00
use Appwrite\Utopia\Request\Filters\V18 as RequestV18 ;
2025-03-12 11:10:10 +00:00
use Appwrite\Utopia\Request\Filters\V19 as RequestV19 ;
2024-03-06 17:34:21 +00:00
use Appwrite\Utopia\Response ;
2023-08-22 18:13:37 +00:00
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16 ;
2024-01-16 11:10:07 +00:00
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17 ;
2024-07-17 05:52:17 +00:00
use Appwrite\Utopia\Response\Filters\V18 as ResponseV18 ;
2025-03-12 11:10:10 +00:00
use Appwrite\Utopia\Response\Filters\V19 as ResponseV19 ;
2024-03-06 17:34:21 +00:00
use Appwrite\Utopia\View ;
use Executor\Executor ;
use MaxMind\Db\Reader ;
use Swoole\Http\Request as SwooleRequest ;
use Utopia\App ;
2020-10-29 22:04:53 +00:00
use Utopia\CLI\Console ;
2024-03-06 17:34:21 +00:00
use Utopia\Config\Config ;
2022-05-26 14:46:08 +00:00
use Utopia\Database\Database ;
2024-10-04 22:23:18 +00:00
use Utopia\Database\DateTime ;
2021-05-16 09:18:34 +00:00
use Utopia\Database\Document ;
2024-02-24 18:01:00 +00:00
use Utopia\Database\Helpers\ID ;
2024-11-22 17:16:51 +00:00
use Utopia\Database\Query ;
2021-07-25 14:51:04 +00:00
use Utopia\Database\Validator\Authorization ;
2024-03-06 17:34:21 +00:00
use Utopia\Domains\Domain ;
2024-05-08 04:25:12 +00:00
use Utopia\DSN\DSN ;
2024-03-06 17:34:21 +00:00
use Utopia\Locale\Locale ;
2024-09-05 22:22:28 +00:00
use Utopia\Logger\Adapter\Sentry ;
2024-03-06 17:34:21 +00:00
use Utopia\Logger\Log ;
use Utopia\Logger\Log\User ;
use Utopia\Logger\Logger ;
2024-11-18 08:52:16 +00:00
use Utopia\Platform\Service ;
2024-04-01 11:08:46 +00:00
use Utopia\System\System ;
2022-02-11 08:44:04 +00:00
use Utopia\Validator\Text ;
2019-05-09 06:54:39 +00:00
2020-06-28 20:45:36 +00:00
Config :: setParam ( 'domainVerification' , false );
2020-07-01 06:35:57 +00:00
Config :: setParam ( 'cookieDomain' , 'localhost' );
Config :: setParam ( 'cookieSamesite' , Response :: COOKIE_SAMESITE_NONE );
2020-06-29 21:43:34 +00:00
2025-06-19 14:15:43 +00:00
function router ( App $utopia , Database $dbForPlatform , callable $getProjectDB , SwooleRequest $swooleRequest , Request $request , Response $response , Log $log , Event $queueForEvents , StatsUsage $queueForStatsUsage , Func $queueForFunctions , Executor $executor , Reader $geodb , callable $isResourceBlocked , string $previewHostname , ? Key $apiKey )
2024-04-22 15:58:40 +00:00
{
$host = $request -> getHostname () ? ? '' ;
2024-11-06 16:05:58 +00:00
if ( ! empty ( $previewHostname )) {
$host = $previewHostname ;
2024-11-06 10:52:43 +00:00
}
2024-04-22 15:58:40 +00:00
2024-11-22 17:16:51 +00:00
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
2024-11-28 10:33:00 +00:00
if ( System :: getEnv ( '_APP_RULES_FORMAT' ) === 'md5' ) {
2025-02-03 09:05:30 +00:00
$rule = Authorization :: skip ( fn () => $dbForPlatform -> getDocument ( 'rules' , md5 ( $host )));
2024-11-28 10:08:24 +00:00
} else {
2025-02-03 09:05:30 +00:00
$rule = Authorization :: skip (
2024-12-12 10:30:26 +00:00
fn () => $dbForPlatform -> find ( 'rules' , [
2024-11-22 17:16:51 +00:00
Query :: equal ( 'domain' , [ $host ]),
Query :: limit ( 1 )
])
)[ 0 ] ? ? new Document ();
}
2024-11-08 21:08:13 +00:00
2025-04-09 04:43:54 +00:00
$errorView = __DIR__ . '/../views/general/error.phtml' ;
2025-06-16 13:46:20 +00:00
$url = ( System :: getEnv ( '_APP_OPTIONS_FORCE_HTTPS' ) == 'disabled' ? 'http' : 'https' ) . '://' . System :: getEnv ( '_APP_CONSOLE_DOMAIN' , System :: getEnv ( '_APP_DOMAIN' , '' ));
2025-04-11 14:52:19 +00:00
2024-11-13 07:48:21 +00:00
if ( $rule -> isEmpty ()) {
2025-04-11 14:52:19 +00:00
$appDomainFunctionsFallback = System :: getEnv ( '_APP_DOMAIN_FUNCTIONS_FALLBACK' , '' );
2025-04-25 10:18:04 +00:00
$appDomainFunctions = System :: getEnv ( '_APP_DOMAIN_FUNCTIONS' , '' );
$appDomainSites = System :: getEnv ( '_APP_DOMAIN_SITES' , '' );
2025-04-11 14:52:19 +00:00
if ( ! empty ( $appDomainFunctionsFallback ) && \str_ends_with ( $host , $appDomainFunctionsFallback )) {
2025-04-25 10:18:04 +00:00
$appDomainFunctions = $appDomainFunctionsFallback ;
2025-04-11 14:52:19 +00:00
}
2025-04-25 10:18:04 +00:00
if ( $host === $appDomainFunctions || $host === $appDomainSites ) {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_ACCESS_FORBIDDEN , 'This domain cannot be used for security reasons. Please use any subdomain instead.' , view : $errorView );
2024-04-22 15:58:40 +00:00
}
2025-04-25 10:18:04 +00:00
if ( \str_ends_with ( $host , $appDomainFunctions ) || \str_ends_with ( $host , $appDomainSites )) {
2025-04-09 04:43:54 +00:00
$exception = new AppwriteException ( AppwriteException :: RULE_NOT_FOUND , 'This domain is not connected to any Appwrite resources. Visit domains tab under function/site settings to configure it.' , view : $errorView );
$exception -> addCTA ( 'Start with this domain' , $url . '/console' );
throw $exception ;
2024-04-22 15:58:40 +00:00
}
2025-02-19 20:59:04 +00:00
if ( System :: getEnv ( '_APP_OPTIONS_ROUTER_PROTECTION' , 'disabled' ) === 'enabled' ) {
if ( $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL && $host !== System :: getEnv ( '_APP_CONSOLE_DOMAIN' , '' )) {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_ACCESS_FORBIDDEN , 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.' , view : $errorView );
2025-02-19 20:59:04 +00:00
}
}
2025-02-19 08:11:47 +00:00
2024-04-22 15:58:40 +00:00
// Act as API - no Proxy logic
return false ;
}
2024-10-24 17:20:00 +00:00
$projectId = $rule -> getAttribute ( 'projectId' );
2023-03-10 12:20:24 +00:00
$project = Authorization :: skip (
2024-12-12 10:30:26 +00:00
fn () => $dbForPlatform -> getDocument ( 'projects' , $projectId )
2024-04-22 15:58:40 +00:00
);
2024-12-10 11:34:51 +00:00
if ( ! $project -> isEmpty () && $project -> getId () !== 'console' ) {
2025-04-17 14:13:09 +00:00
$accessedAt = $project -> getAttribute ( 'accessedAt' , 0 );
2024-12-10 11:34:51 +00:00
if ( DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), - APP_PROJECT_ACCESS )) > $accessedAt ) {
$project -> setAttribute ( 'accessedAt' , DateTime :: now ());
2024-12-17 14:05:37 +00:00
Authorization :: skip ( fn () => $dbForPlatform -> updateDocument ( 'projects' , $project -> getId (), $project ));
2024-12-10 11:34:51 +00:00
}
2025-06-19 14:15:43 +00:00
2025-06-15 08:30:57 +00:00
/**
2025-06-19 14:15:43 +00:00
* Set projectId to update the Error hook logger , since x - appwrite - project is not available when executing custom domain function
2025-06-15 08:30:57 +00:00
*/
2025-06-19 14:15:43 +00:00
$log -> addTag ( 'projectId' , $project -> getId ());
2024-12-10 11:34:51 +00:00
}
2024-04-22 15:58:40 +00:00
if ( array_key_exists ( 'proxy' , $project -> getAttribute ( 'services' , []))) {
$status = $project -> getAttribute ( 'services' , [])[ 'proxy' ];
if ( ! $status ) {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVICE_DISABLED , view : $errorView );
2024-04-22 15:58:40 +00:00
}
}
// Skip Appwrite Router for ACME challenge. Nessessary for certificate generation
2023-08-06 13:11:30 +00:00
$path = ( $swooleRequest -> server [ 'request_uri' ] ? ? '/' );
2024-04-22 15:58:40 +00:00
if ( \str_starts_with ( $path , '/.well-known/acme-challenge' )) {
return false ;
}
2025-02-23 20:34:14 +00:00
$type = $rule -> getAttribute ( 'type' , '' );
2024-09-05 10:17:30 +00:00
2025-02-23 20:34:14 +00:00
if ( $type === 'deployment' ) {
2025-04-16 08:43:02 +00:00
if ( System :: getEnv ( '_APP_OPTIONS_ROUTER_FORCE_HTTPS' , 'disabled' ) === 'enabled' ) { // Force HTTPS
2025-02-21 22:45:45 +00:00
if ( $request -> getProtocol () !== 'https' && $request -> getHostname () !== APP_HOSTNAME_INTERNAL ) {
2024-04-22 15:58:40 +00:00
if ( $request -> getMethod () !== Request :: METHOD_GET ) {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_PROTOCOL_UNSUPPORTED , 'Method unsupported over HTTP. Please use HTTPS instead.' , view : $errorView );
2024-04-22 15:58:40 +00:00
}
return $response -> redirect ( 'https://' . $request -> getHostname () . $request -> getURI ());
}
}
2025-02-23 20:34:14 +00:00
/** @var Database $dbForProject */
$dbForProject = $getProjectDB ( $project );
2025-04-03 13:19:41 +00:00
/** @var Document $deployment */
2025-04-29 09:56:51 +00:00
if ( ! empty ( $rule -> getAttribute ( 'deploymentId' , '' ))) {
$deployment = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'deployments' , $rule -> getAttribute ( 'deploymentId' )));
2025-04-29 09:31:06 +00:00
} else {
// 1.6.x DB schema compatibility
// TODO: Make sure deploymentId is never empty, and remove this code
2025-04-29 09:56:51 +00:00
// Check if site or function; should never be site, but better safe than sorry
// Attempts to use attribute from both schemas (1.6 and 1.7)
$resourceType = $rule -> getAttribute ( 'deploymentResourceType' , $rule -> getAttribute ( 'resourceType' , '' ));
// ID of site or function
$resourceId = $rule -> getAttribute ( 'deploymentResourceId' , '' );
// Document of site or function
$resource = $resourceType === 'function' ?
Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'functions' , $resourceId )) :
Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'sites' , $resourceId ));
// ID of active deployments
// Attempts to use attribute from both schemas (1.6 and 1.7)
$activeDeploymentId = $resource -> getAttribute ( 'deploymentId' , $resource -> getAttribute ( 'deployment' , '' ));
// Get deployment document, as intended originally
$deployment = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'deployments' , $activeDeploymentId ));
}
2025-02-23 20:34:14 +00:00
2025-02-24 10:55:59 +00:00
if ( $deployment -> getAttribute ( 'resourceType' , '' ) === 'functions' ) {
2025-02-23 20:34:14 +00:00
$type = 'function' ;
2025-02-24 10:55:59 +00:00
} elseif ( $deployment -> getAttribute ( 'resourceType' , '' ) === 'sites' ) {
2025-02-23 20:34:14 +00:00
$type = 'site' ;
}
2025-04-03 13:19:41 +00:00
if ( $deployment -> isEmpty ()) {
2025-04-09 17:50:06 +00:00
$resourceType = $rule -> getAttribute ( 'deploymentResourceType' , '' );
$resourceId = $rule -> getAttribute ( 'deploymentResourceId' , '' );
$type = ( $resourceType === 'site' ) ? 'sites' : 'functions' ;
2025-04-09 04:43:54 +00:00
$exception = new AppwriteException ( AppwriteException :: DEPLOYMENT_NOT_FOUND , view : $errorView );
2025-06-12 06:13:11 +00:00
$exception -> addCTA ( 'View deployments' , $url . '/console/project-' . $project -> getAttribute ( 'region' , 'default' ) . '-' . $projectId . '/' . $type . '/' . $resourceType . '-' . $resourceId );
2025-04-09 04:43:54 +00:00
throw $exception ;
2025-04-03 13:19:41 +00:00
}
2025-02-23 20:34:14 +00:00
$resource = $type === 'function' ?
2025-02-24 10:55:59 +00:00
Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'functions' , $deployment -> getAttribute ( 'resourceId' , '' ))) :
Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'sites' , $deployment -> getAttribute ( 'resourceId' , '' )));
2025-02-23 20:34:14 +00:00
2025-03-13 09:33:28 +00:00
$isPreview = $type === 'function' ? false : ( $rule -> getAttribute ( 'trigger' , '' ) !== 'manual' );
2024-04-22 15:58:40 +00:00
2023-08-06 13:11:30 +00:00
$path = ( $swooleRequest -> server [ 'request_uri' ] ? ? '/' );
2023-02-27 12:43:20 +00:00
$query = ( $swooleRequest -> server [ 'query_string' ] ? ? '' );
2024-04-22 15:58:40 +00:00
if ( ! empty ( $query )) {
$path .= '?' . $query ;
}
2025-02-10 14:07:01 +00:00
$protocol = $request -> getProtocol ();
2024-02-22 09:40:18 +00:00
2025-03-08 17:45:38 +00:00
/**
Ensure preview authorization
- Authorization is skippable for tests , and build screenshot
- If cookie is not sent by client -> not authorized
- If JWT in cookie is invalid or expired -> not authorized
- If user is blocked or removed -> not authorized
- If user ' s session is removed or expired -> not authorized
- If user is not member of team of this deployment -> not authorized
- If not authorized , redirect to Console redirect UI
- If authorized , continue as if auth was not required
*/
2025-03-08 11:57:55 +00:00
$requirePreview = \is_null ( $apiKey ) || ! $apiKey -> isPreviewAuthDisabled ();
if ( $isPreview && $requirePreview ) {
2025-02-10 14:07:01 +00:00
$cookie = $request -> getCookie ( Auth :: $cookieNamePreview , '' );
2025-03-08 11:57:55 +00:00
$authorized = false ;
2025-02-10 14:07:01 +00:00
2025-03-08 11:57:55 +00:00
// Security checks to mark authorized true
if ( ! empty ( $cookie )) {
2025-02-10 14:07:01 +00:00
$jwt = new JWT ( System :: getEnv ( '_APP_OPENSSL_KEY_V1' ), 'HS256' , 3600 , 0 );
$payload = [];
try {
$payload = $jwt -> decode ( $cookie );
} catch ( JWTException $error ) {
2025-03-08 11:57:55 +00:00
// Authorized remains false
2025-02-10 14:07:01 +00:00
}
2024-04-22 15:58:40 +00:00
2025-03-08 11:57:55 +00:00
$userExists = false ;
$userId = $payload [ 'userId' ] ? ? '' ;
if ( ! empty ( $userId )) {
$user = Authorization :: skip ( fn () => $dbForPlatform -> getDocument ( 'users' , $userId ));
if ( ! $user -> isEmpty () && $user -> getAttribute ( 'status' , false )) {
$userExists = true ;
2025-02-10 14:07:01 +00:00
}
}
2024-04-22 15:58:40 +00:00
2025-03-08 11:57:55 +00:00
$sessionExists = false ;
2025-02-10 14:07:01 +00:00
$jwtSessionId = $payload [ 'sessionId' ] ? ? '' ;
2025-03-08 11:57:55 +00:00
if ( ! empty ( $jwtSessionId ) && ! empty ( $user -> find ( '$id' , $jwtSessionId , 'sessions' ))) {
$sessionExists = true ;
}
2024-04-22 15:58:40 +00:00
2025-03-08 11:57:55 +00:00
$membershipExists = false ;
$project = Authorization :: skip ( fn () => $dbForPlatform -> getDocument ( 'projects' , $projectId ));
2025-03-09 17:51:49 +00:00
if ( ! $project -> isEmpty () && isset ( $user )) {
2025-03-08 11:57:55 +00:00
$teamId = $project -> getAttribute ( 'teamId' , '' );
$membership = $user -> find ( 'teamId' , $teamId , 'memberships' );
if ( ! empty ( $membership )) {
$membershipExists = true ;
2025-02-10 14:07:01 +00:00
}
}
2024-04-22 15:58:40 +00:00
2025-03-08 11:57:55 +00:00
if ( $userExists && $sessionExists && $membershipExists ) {
$authorized = true ;
}
2025-02-10 14:07:01 +00:00
}
2024-04-22 15:58:40 +00:00
2025-03-08 11:57:55 +00:00
if ( ! $authorized ) {
2025-06-16 13:17:50 +00:00
$url = ( System :: getEnv ( '_APP_OPTIONS_FORCE_HTTPS' ) == 'disabled' ? 'http' : 'https' ) . " :// " . System :: getEnv ( '_APP_CONSOLE_DOMAIN' , System :: getEnv ( '_APP_DOMAIN' , '' ));
2025-02-10 14:07:01 +00:00
$response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> redirect ( $url . '/console/auth/preview?'
. \http_build_query ([
'projectId' => $projectId ,
'origin' => $protocol . '://' . $host ,
'path' => $path
]));
return true ;
}
2024-04-22 15:58:40 +00:00
}
2024-10-08 07:54:40 +00:00
$body = $swooleRequest -> getContent () ? ? '' ;
$method = $swooleRequest -> server [ 'request_method' ];
2024-10-17 11:32:42 +00:00
2023-10-27 15:25:19 +00:00
$requestHeaders = $request -> getHeaders ();
2024-04-22 15:58:40 +00:00
2024-10-24 17:20:00 +00:00
if ( $resource -> isEmpty () || ! $resource -> getAttribute ( 'enabled' )) {
2025-04-03 13:19:41 +00:00
if ( $type === 'functions' ) {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: FUNCTION_NOT_FOUND , view : $errorView );
2025-04-03 13:19:41 +00:00
} else {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: SITE_NOT_FOUND , view : $errorView );
2025-04-03 13:19:41 +00:00
}
2024-02-22 09:40:18 +00:00
}
2024-04-22 15:58:40 +00:00
2025-02-23 20:34:14 +00:00
if ( $isResourceBlocked ( $project , $type === 'function' ? RESOURCE_TYPE_FUNCTIONS : RESOURCE_TYPE_SITES , $resource -> getId ())) {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_RESOURCE_BLOCKED , view : $errorView );
2024-04-22 15:58:40 +00:00
}
2025-02-13 18:57:41 +00:00
$version = match ( $type ) {
2024-10-26 16:43:30 +00:00
'function' => $resource -> getAttribute ( 'version' , 'v2' ),
2025-03-11 17:19:25 +00:00
'site' => 'v5' ,
2024-10-26 16:43:30 +00:00
};
2024-04-22 15:58:40 +00:00
2024-02-22 09:40:18 +00:00
$runtimes = Config :: getParam ( $version === 'v2' ? 'runtimes-v2' : 'runtimes' , []);
2025-03-07 21:36:13 +00:00
$spec = Config :: getParam ( 'specifications' )[ $resource -> getAttribute ( 'specification' , APP_COMPUTE_SPECIFICATION_DEFAULT )];
2024-04-22 15:58:40 +00:00
2025-02-13 18:57:41 +00:00
$runtime = match ( $type ) {
2024-10-26 16:43:30 +00:00
'function' => $runtimes [ $resource -> getAttribute ( 'runtime' )] ? ? null ,
2024-12-02 12:20:54 +00:00
'site' => $runtimes [ $resource -> getAttribute ( 'buildRuntime' )] ? ? null ,
2024-10-24 17:20:00 +00:00
default => null
};
2024-04-22 15:58:40 +00:00
2025-02-28 09:40:28 +00:00
// Static site enforced runtime
2025-03-24 14:34:02 +00:00
if ( $deployment -> getAttribute ( 'adapter' , '' ) === 'static' ) {
2025-02-28 09:40:28 +00:00
$runtime = $runtimes [ 'static-1' ] ? ? null ;
2024-04-22 15:58:40 +00:00
}
2024-10-24 17:20:00 +00:00
if ( \is_null ( $runtime )) {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: FUNCTION_RUNTIME_UNSUPPORTED , 'Runtime "' . $resource -> getAttribute ( 'runtime' , '' ) . '" is not supported' , view : $errorView );
2024-04-22 15:58:40 +00:00
}
2025-03-26 11:59:05 +00:00
$allowAnyStatus = ! \is_null ( $apiKey ) && $apiKey -> isDeploymentStatusIgnored ();
if ( ! $allowAnyStatus && $deployment -> getAttribute ( 'status' ) !== 'ready' ) {
2025-04-09 04:43:54 +00:00
$status = $deployment -> getAttribute ( 'status' );
2025-06-12 06:13:11 +00:00
$region = $project -> getAttribute ( 'region' , 'default' );
2025-04-09 04:43:54 +00:00
switch ( $status ) {
case 'failed' :
$exception = new AppwriteException ( AppwriteException :: BUILD_FAILED , view : $errorView );
2025-06-12 06:13:11 +00:00
$ctaUrl = '/console/project-' . $region . '-' . $project -> getId () . '/sites/site-' . $resource -> getId () . '/deployments/deployment-' . $deployment -> getId ();
2025-04-09 04:43:54 +00:00
$exception -> addCTA ( 'View logs' , $url . $ctaUrl );
break ;
case 'canceled' :
$exception = new AppwriteException ( AppwriteException :: BUILD_CANCELED , view : $errorView );
2025-06-12 06:13:11 +00:00
$ctaUrl = '/console/project-' . $region . '-' . $project -> getId () . '/sites/site-' . $resource -> getId () . '/deployments' ;
2025-04-09 04:43:54 +00:00
$exception -> addCTA ( 'View deployments' , $url . $ctaUrl );
break ;
default :
$exception = new AppwriteException ( AppwriteException :: BUILD_NOT_READY , view : $errorView );
2025-06-12 06:13:11 +00:00
$ctaUrl = '/console/project-' . $region . '-' . $project -> getId () . '/sites/site-' . $resource -> getId () . '/deployments/deployment-' . $deployment -> getId ();
2025-04-09 04:43:54 +00:00
$exception -> addCTA ( 'Reload' , '/' );
$exception -> addCTA ( 'View logs' , $url . $ctaUrl );
break ;
2025-03-26 11:22:32 +00:00
}
2025-04-09 04:43:54 +00:00
throw $exception ;
2024-04-22 15:58:40 +00:00
}
2024-10-24 17:20:00 +00:00
if ( $type === 'function' ) {
$permissions = $resource -> getAttribute ( 'execute' );
2024-10-23 16:36:26 +00:00
if ( ! ( \in_array ( 'any' , $permissions )) && ! ( \in_array ( 'guests' , $permissions ))) {
2025-04-15 12:16:04 +00:00
$exception = new AppwriteException ( AppwriteException :: FUNCTION_EXECUTE_PERMISSION_MISSING , view : $errorView );
2025-06-12 06:13:11 +00:00
$exception -> addCTA ( 'View settings' , $url . '/console/project-' . $project -> getAttribute ( 'region' , 'default' ) . '-' . $project -> getId () . '/functions/function-' . $resource -> getId () . '/settings' );
2025-04-15 12:16:04 +00:00
throw $exception ;
2024-10-23 16:36:26 +00:00
}
2024-02-22 09:40:18 +00:00
}
2024-07-23 12:28:13 +00:00
2024-04-22 15:58:40 +00:00
$headers = \array_merge ([], $requestHeaders );
$headers [ 'x-appwrite-user-id' ] = '' ;
$headers [ 'x-appwrite-country-code' ] = '' ;
$headers [ 'x-appwrite-continent-code' ] = '' ;
$headers [ 'x-appwrite-continent-eu' ] = 'false' ;
2025-02-23 20:34:14 +00:00
$jwtExpiry = $resource -> getAttribute ( 'timeout' , 900 );
$jwtObj = new JWT ( System :: getEnv ( '_APP_OPENSSL_KEY_V1' ), 'HS256' , $jwtExpiry , 0 );
$jwtKey = $jwtObj -> encode ([
'projectId' => $project -> getId (),
'scopes' => $resource -> getAttribute ( 'scopes' , [])
]);
$headers [ 'x-appwrite-key' ] = API_KEY_DYNAMIC . '_' . $jwtKey ;
$headers [ 'x-appwrite-trigger' ] = 'http' ;
$headers [ 'x-appwrite-user-jwt' ] = '' ;
2024-10-24 17:20:00 +00:00
2024-04-22 15:58:40 +00:00
$ip = $headers [ 'x-real-ip' ] ? ? '' ;
if ( ! empty ( $ip )) {
$record = $geodb -> get ( $ip );
if ( $record ) {
$eu = Config :: getParam ( 'locale-eu' );
$headers [ 'x-appwrite-country-code' ] = $record [ 'country' ][ 'iso_code' ] ? ? '' ;
$headers [ 'x-appwrite-continent-code' ] = $record [ 'continent' ][ 'code' ] ? ? '' ;
$headers [ 'x-appwrite-continent-eu' ] = ( \in_array ( $record [ 'country' ][ 'iso_code' ], $eu )) ? 'true' : 'false' ;
}
}
$headersFiltered = [];
foreach ( $headers as $key => $value ) {
if ( \in_array ( \strtolower ( $key ), FUNCTION_ALLOWLIST_HEADERS_REQUEST )) {
$headersFiltered [] = [ 'name' => $key , 'value' => $value ];
}
}
$executionId = ID :: unique ();
$execution = new Document ([
'$id' => $executionId ,
'$permissions' => [],
2025-05-27 01:36:23 +00:00
'resourceInternalId' => $resource -> getSequence (),
2024-12-02 13:00:06 +00:00
'resourceId' => $resource -> getId (),
2025-05-26 05:42:11 +00:00
'deploymentInternalId' => $deployment -> getSequence (),
2024-04-22 15:58:40 +00:00
'deploymentId' => $deployment -> getId (),
'responseStatusCode' => 0 ,
'responseHeaders' => [],
'requestPath' => $path ,
'requestMethod' => $method ,
'requestHeaders' => $headersFiltered ,
'errors' => '' ,
'logs' => '' ,
'duration' => 0.0 ,
]);
2024-12-02 13:00:06 +00:00
if ( $type === 'function' ) {
$execution -> setAttribute ( 'resourceType' , 'functions' );
$execution -> setAttribute ( 'trigger' , 'http' ); // http / schedule / event
$execution -> setAttribute ( 'status' , 'processing' ); // waiting / processing / completed / failed
2025-02-23 20:34:14 +00:00
$queueForEvents
2025-02-25 11:53:12 +00:00
-> setParam ( 'functionId' , $resource -> getId ())
-> setParam ( 'executionId' , $execution -> getId ())
-> setContext ( 'function' , $resource );
2024-12-02 13:00:06 +00:00
} elseif ( $type === 'site' ) {
$execution -> setAttribute ( 'resourceType' , 'sites' );
2025-02-23 20:34:14 +00:00
$queueForEvents
-> setParam ( 'siteId' , $resource -> getId ())
-> setParam ( 'executionId' , $execution -> getId ())
-> setContext ( 'site' , $resource );
}
2024-04-22 15:58:40 +00:00
$durationStart = \microtime ( true );
$vars = [];
// V2 vars
if ( $version === 'v2' ) {
$vars = \array_merge ( $vars , [
'APPWRITE_FUNCTION_TRIGGER' => $headers [ 'x-appwrite-trigger' ] ? ? '' ,
'APPWRITE_FUNCTION_DATA' => $body ? ? '' ,
'APPWRITE_FUNCTION_USER_ID' => $headers [ 'x-appwrite-user-id' ] ? ? '' ,
'APPWRITE_FUNCTION_JWT' => $headers [ 'x-appwrite-user-jwt' ] ? ? ''
]);
}
// Shared vars
2024-10-24 17:20:00 +00:00
foreach ( $resource -> getAttribute ( 'varsProject' , []) as $var ) {
2024-04-22 15:58:40 +00:00
$vars [ $var -> getAttribute ( 'key' )] = $var -> getAttribute ( 'value' , '' );
}
// Function vars
2024-10-24 17:20:00 +00:00
foreach ( $resource -> getAttribute ( 'vars' , []) as $var ) {
2024-04-22 15:58:40 +00:00
$vars [ $var -> getAttribute ( 'key' )] = $var -> getAttribute ( 'value' , '' );
}
2025-06-18 05:36:35 +00:00
$protocol = System :: getEnv ( '_APP_OPTIONS_FORCE_HTTPS' ) === 'disabled' ? 'http' : 'https' ;
2024-07-23 12:28:13 +00:00
$hostname = System :: getEnv ( '_APP_DOMAIN' );
$endpoint = $protocol . '://' . $hostname . " /v1 " ;
2024-04-22 15:58:40 +00:00
// Appwrite vars
2025-05-18 21:53:56 +00:00
if ( $type === 'function' ) {
2025-05-18 21:53:43 +00:00
$vars = \array_merge ( $vars , [
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint ,
'APPWRITE_FUNCTION_ID' => $resource -> getId (),
'APPWRITE_FUNCTION_NAME' => $resource -> getAttribute ( 'name' ),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment -> getId (),
'APPWRITE_FUNCTION_PROJECT_ID' => $project -> getId (),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime [ 'name' ] ? ? '' ,
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime [ 'version' ] ? ? '' ,
'APPWRITE_FUNCTION_CPUS' => $spec [ 'cpus' ] ? ? APP_COMPUTE_CPUS_DEFAULT ,
'APPWRITE_FUNCTION_MEMORY' => $spec [ 'memory' ] ? ? APP_COMPUTE_MEMORY_DEFAULT ,
]);
2025-05-18 21:53:56 +00:00
} elseif ( $type === 'site' ) {
2025-05-18 21:53:43 +00:00
$vars = \array_merge ( $vars , [
'APPWRITE_SITE_API_ENDPOINT' => $endpoint ,
'APPWRITE_SITE_ID' => $resource -> getId (),
'APPWRITE_SITE_NAME' => $resource -> getAttribute ( 'name' ),
'APPWRITE_SITE_DEPLOYMENT' => $deployment -> getId (),
'APPWRITE_SITE_PROJECT_ID' => $project -> getId (),
'APPWRITE_SITE_RUNTIME_NAME' => $runtime [ 'name' ] ? ? '' ,
'APPWRITE_SITE_RUNTIME_VERSION' => $runtime [ 'version' ] ? ? '' ,
'APPWRITE_SITE_CPUS' => $spec [ 'cpus' ] ? ? APP_COMPUTE_CPUS_DEFAULT ,
'APPWRITE_SITE_MEMORY' => $spec [ 'memory' ] ? ? APP_COMPUTE_MEMORY_DEFAULT ,
]);
}
2025-05-18 21:53:56 +00:00
2024-04-22 15:58:40 +00:00
$vars = \array_merge ( $vars , [
2024-07-15 07:10:11 +00:00
'APPWRITE_VERSION' => APP_VERSION_STABLE ,
'APPWRITE_REGION' => $project -> getAttribute ( 'region' ),
2024-09-06 10:50:29 +00:00
'APPWRITE_DEPLOYMENT_TYPE' => $deployment -> getAttribute ( 'type' , '' ),
'APPWRITE_VCS_REPOSITORY_ID' => $deployment -> getAttribute ( 'providerRepositoryId' , '' ),
'APPWRITE_VCS_REPOSITORY_NAME' => $deployment -> getAttribute ( 'providerRepositoryName' , '' ),
'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment -> getAttribute ( 'providerRepositoryOwner' , '' ),
'APPWRITE_VCS_REPOSITORY_URL' => $deployment -> getAttribute ( 'providerRepositoryUrl' , '' ),
'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment -> getAttribute ( 'providerBranch' , '' ),
'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment -> getAttribute ( 'providerBranchUrl' , '' ),
'APPWRITE_VCS_COMMIT_HASH' => $deployment -> getAttribute ( 'providerCommitHash' , '' ),
'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment -> getAttribute ( 'providerCommitMessage' , '' ),
'APPWRITE_VCS_COMMIT_URL' => $deployment -> getAttribute ( 'providerCommitUrl' , '' ),
'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment -> getAttribute ( 'providerCommitAuthor' , '' ),
'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment -> getAttribute ( 'providerCommitAuthorUrl' , '' ),
'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment -> getAttribute ( 'providerRootDirectory' , '' ),
2024-04-22 15:58:40 +00:00
]);
2025-02-08 09:36:11 +00:00
// SPA fallbackFile override
2025-03-24 14:34:02 +00:00
if ( $deployment -> getAttribute ( 'adapter' , '' ) === 'static' && $deployment -> getAttribute ( 'fallbackFile' , '' ) !== '' ) {
$vars [ 'OPEN_RUNTIMES_STATIC_FALLBACK' ] = $deployment -> getAttribute ( 'fallbackFile' , '' );
2025-02-08 09:36:11 +00:00
}
2024-04-22 15:58:40 +00:00
/** Execute function */
try {
2025-02-13 18:57:41 +00:00
$version = match ( $type ) {
2024-10-24 17:20:00 +00:00
'function' => $resource -> getAttribute ( 'version' , 'v2' ),
2025-03-11 17:19:25 +00:00
'site' => 'v5' ,
2024-10-24 17:20:00 +00:00
};
2025-02-13 18:57:41 +00:00
$entrypoint = match ( $type ) {
2024-10-24 17:20:00 +00:00
'function' => $deployment -> getAttribute ( 'entrypoint' , '' ),
2024-10-27 20:10:53 +00:00
'site' => '' ,
2024-10-24 17:20:00 +00:00
};
2025-06-13 10:15:32 +00:00
$source = $deployment -> getAttribute ( 'buildPath' , '' );
$extension = str_ends_with ( $source , '.tar' ) ? 'tar' : 'tar.gz' ;
2024-11-27 15:27:40 +00:00
2025-06-13 10:15:32 +00:00
$startCommand = $runtime [ 'startCommand' ];
if ( $type === 'site' ) {
2024-11-27 15:27:40 +00:00
$frameworks = Config :: getParam ( 'frameworks' , []);
$framework = $frameworks [ $resource -> getAttribute ( 'framework' , '' )] ? ? null ;
2024-12-02 12:45:03 +00:00
if ( ! is_null ( $framework )) {
2025-03-24 14:34:02 +00:00
$adapter = ( $framework [ 'adapters' ] ? ? [])[ $deployment -> getAttribute ( 'adapter' , '' )] ? ? null ;
2024-12-02 12:45:03 +00:00
if ( ! is_null ( $adapter ) && isset ( $adapter [ 'startCommand' ])) {
2024-12-02 12:20:54 +00:00
$startCommand = $adapter [ 'startCommand' ];
}
2024-11-27 15:27:40 +00:00
}
}
2025-06-13 10:15:32 +00:00
$runtimeEntrypoint = match ( $version ) {
'v2' => '' ,
default => " cp /tmp/code. $extension /mnt/code/code. $extension && nohup helpers/start.sh \" $startCommand\ " " ,
};
2025-02-13 18:57:41 +00:00
$entrypoint = match ( $type ) {
2024-11-27 15:27:40 +00:00
'function' => $deployment -> getAttribute ( 'entrypoint' , '' ),
'site' => '' ,
2024-10-24 17:20:00 +00:00
};
2024-04-22 15:58:40 +00:00
$executionResponse = $executor -> createExecution (
projectId : $project -> getId (),
deploymentId : $deployment -> getId (),
body : \strlen ( $body ) > 0 ? $body : null ,
variables : $vars ,
2024-10-24 17:20:00 +00:00
timeout : $resource -> getAttribute ( 'timeout' , 30 ),
2024-04-22 15:58:40 +00:00
image : $runtime [ 'image' ],
2025-06-13 10:15:32 +00:00
source : $source ,
2024-10-23 16:36:26 +00:00
entrypoint : $entrypoint ,
2024-04-22 15:58:40 +00:00
version : $version ,
path : $path ,
method : $method ,
headers : $headers ,
2024-10-23 16:36:26 +00:00
runtimeEntrypoint : $runtimeEntrypoint ,
2024-10-27 16:22:45 +00:00
cpus : $spec [ 'cpus' ] ? ? APP_COMPUTE_CPUS_DEFAULT ,
memory : $spec [ 'memory' ] ? ? APP_COMPUTE_MEMORY_DEFAULT ,
2024-10-24 17:20:00 +00:00
logging : $resource -> getAttribute ( 'logging' , true ),
2024-02-22 09:40:18 +00:00
requestTimeout : 30
2024-04-22 15:58:40 +00:00
);
2025-02-08 09:36:11 +00:00
// Branded 404 override
2025-03-31 16:51:33 +00:00
$isResponseBranded = false ;
2025-03-24 14:34:02 +00:00
if ( $executionResponse [ 'statusCode' ] === 404 && $deployment -> getAttribute ( 'adapter' , '' ) === 'static' ) {
2025-02-08 09:36:11 +00:00
$layout = new View ( __DIR__ . '/../views/general/404.phtml' );
$executionResponse [ 'body' ] = $layout -> render ();
$executionResponse [ 'headers' ][ 'content-length' ] = \strlen ( $executionResponse [ 'body' ]);
2025-03-31 16:51:33 +00:00
$isResponseBranded = true ;
2025-02-08 09:36:11 +00:00
}
2025-02-08 14:53:55 +00:00
// Branded banner for previews
2025-03-31 16:51:33 +00:00
if ( ! $isResponseBranded ) {
if ( \is_null ( $apiKey ) || $apiKey -> isBannerDisabled () === false ) {
$transformation = new Transformation ();
$transformation -> addAdapter ( new Preview ());
$transformation -> setInput ( $executionResponse [ 'body' ]);
$transformation -> setTraits ( $executionResponse [ 'headers' ]);
if ( $isPreview && $transformation -> transform ()) {
$executionResponse [ 'body' ] = $transformation -> getOutput ();
foreach ( $executionResponse [ 'headers' ] as $key => $value ) {
if ( \strtolower ( $key ) === 'content-length' ) {
$executionResponse [ 'headers' ][ $key ] = \strlen ( $executionResponse [ 'body' ]);
}
2025-02-21 19:50:43 +00:00
}
2025-02-08 14:53:55 +00:00
}
}
}
2025-04-15 10:48:25 +00:00
2025-04-15 09:40:32 +00:00
// Branded error pages (when developer left body empty)
if ( $executionResponse [ 'statusCode' ] >= 400 && empty ( $executionResponse [ 'body' ])) {
$layout = new View ( $errorView );
$layout
-> setParam ( 'title' , $project -> getAttribute ( 'name' ) . ' - Error' )
2025-04-15 13:17:43 +00:00
-> setParam ( 'type' , 'proxy_error_override' )
2025-04-15 09:40:32 +00:00
-> setParam ( 'code' , $executionResponse [ 'statusCode' ]);
$executionResponse [ 'body' ] = $layout -> render ();
foreach ( $executionResponse [ 'headers' ] as $key => $value ) {
if ( \strtolower ( $key ) === 'content-length' ) {
$executionResponse [ 'headers' ][ $key ] = \strlen ( $executionResponse [ 'body' ]);
2025-04-15 12:16:04 +00:00
} elseif ( \strtolower ( $key ) === 'content-type' ) {
$executionResponse [ 'headers' ][ $key ] = 'text/html' ;
2025-04-15 09:40:32 +00:00
}
}
}
2025-02-08 14:53:55 +00:00
2024-04-22 15:58:40 +00:00
$headersFiltered = [];
foreach ( $executionResponse [ 'headers' ] as $key => $value ) {
if ( \in_array ( \strtolower ( $key ), FUNCTION_ALLOWLIST_HEADERS_RESPONSE )) {
$headersFiltered [] = [ 'name' => $key , 'value' => $value ];
}
}
/** Update execution status */
2024-08-08 08:38:15 +00:00
$status = $executionResponse [ 'statusCode' ] >= 500 ? 'failed' : 'completed' ;
2024-04-22 15:58:40 +00:00
$execution -> setAttribute ( 'status' , $status );
$execution -> setAttribute ( 'logs' , $executionResponse [ 'logs' ]);
$execution -> setAttribute ( 'errors' , $executionResponse [ 'errors' ]);
2024-02-22 09:40:18 +00:00
$execution -> setAttribute ( 'responseStatusCode' , $executionResponse [ 'statusCode' ]);
$execution -> setAttribute ( 'responseHeaders' , $headersFiltered );
2024-04-22 15:58:40 +00:00
$execution -> setAttribute ( 'duration' , $executionResponse [ 'duration' ]);
} catch ( \Throwable $th ) {
$durationEnd = \microtime ( true );
$execution
-> setAttribute ( 'duration' , $durationEnd - $durationStart )
2024-12-02 13:00:06 +00:00
-> setAttribute ( 'responseStatusCode' , 500 );
if ( $type === 'function' ) {
$execution
2025-02-13 18:57:41 +00:00
-> setAttribute ( 'status' , 'failed' )
-> setAttribute ( 'errors' , $th -> getMessage () . '\nError Code: ' . $th -> getCode ());
2024-12-02 13:00:06 +00:00
}
2024-04-22 15:58:40 +00:00
Console :: error ( $th -> getMessage ());
2024-05-20 16:16:47 +00:00
if ( $th instanceof AppwriteException ) {
throw $th ;
}
2024-04-22 15:58:40 +00:00
} finally {
2024-10-24 17:20:00 +00:00
if ( $type === 'function' ) {
2024-10-22 15:33:33 +00:00
$queueForFunctions
-> setType ( Func :: TYPE_ASYNC_WRITE )
-> setExecution ( $execution )
-> setProject ( $project )
-> trigger ();
2024-12-02 13:00:06 +00:00
} elseif ( $type === 'site' ) { // TODO: Move it to logs worker later
$dbForProject -> createDocument ( 'executions' , $execution );
2024-10-22 15:33:33 +00:00
}
2024-04-22 15:58:40 +00:00
}
2023-02-27 12:43:20 +00:00
2024-04-22 15:58:40 +00:00
$execution -> setAttribute ( 'logs' , '' );
$execution -> setAttribute ( 'errors' , '' );
2023-02-27 12:43:20 +00:00
2024-04-22 15:58:40 +00:00
$headers = [];
foreach (( $executionResponse [ 'headers' ] ? ? []) as $key => $value ) {
$headers [] = [ 'name' => $key , 'value' => $value ];
}
$execution -> setAttribute ( 'responseBody' , $executionResponse [ 'body' ] ? ? '' );
$execution -> setAttribute ( 'responseHeaders' , $headers );
$body = $execution [ 'responseBody' ] ? ? '' ;
$contentType = 'text/plain' ;
foreach ( $execution [ 'responseHeaders' ] as $header ) {
if ( \strtolower ( $header [ 'name' ]) === 'content-type' ) {
$contentType = $header [ 'value' ];
}
2023-02-27 12:43:20 +00:00
2024-12-02 12:20:54 +00:00
if ( \strtolower ( $header [ 'name' ]) === 'transfer-encoding' ) {
continue ;
}
2024-12-02 16:56:52 +00:00
$response -> addHeader ( \strtolower ( $header [ 'name' ]), $header [ 'value' ]);
2024-04-22 15:58:40 +00:00
}
2023-02-27 12:43:20 +00:00
2024-04-22 15:58:40 +00:00
$response
-> setContentType ( $contentType )
-> setStatusCode ( $execution [ 'responseStatusCode' ] ? ? 200 )
-> send ( $body );
2024-11-06 11:29:24 +00:00
$fileSize = 0 ;
$file = $request -> getFiles ( 'file' );
if ( ! empty ( $file )) {
$fileSize = ( \is_array ( $file [ 'size' ]) && isset ( $file [ 'size' ][ 0 ])) ? $file [ 'size' ][ 0 ] : $file [ 'size' ];
}
2025-04-01 07:31:53 +00:00
if ( ! empty ( $apiKey ) && ! empty ( $apiKey -> getDisabledMetrics ())) {
foreach ( $apiKey -> getDisabledMetrics () as $key ) {
$queueForStatsUsage -> disableMetric ( $key );
}
}
2025-04-02 05:05:24 +00:00
$metricTypeExecutions = str_replace ([ '{resourceType}' ], [ $deployment -> getAttribute ( 'resourceType' )], METRIC_RESOURCE_TYPE_EXECUTIONS );
2025-05-27 01:36:23 +00:00
$metricTypeIdExecutions = str_replace ([ '{resourceType}' , '{resourceInternalId}' ], [ $deployment -> getAttribute ( 'resourceType' ), $resource -> getSequence ()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS );
2025-04-02 05:05:24 +00:00
$metricTypeExecutionsCompute = str_replace ([ '{resourceType}' ], [ $deployment -> getAttribute ( 'resourceType' )], METRIC_RESOURCE_TYPE_EXECUTIONS_COMPUTE );
2025-05-27 01:36:23 +00:00
$metricTypeIdExecutionsCompute = str_replace ([ '{resourceType}' , '{resourceInternalId}' ], [ $deployment -> getAttribute ( 'resourceType' ), $resource -> getSequence ()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_COMPUTE );
2025-04-02 05:05:24 +00:00
$metricTypeExecutionsMbSeconds = str_replace ([ '{resourceType}' ], [ $deployment -> getAttribute ( 'resourceType' )], METRIC_RESOURCE_TYPE_EXECUTIONS_MB_SECONDS );
2025-05-27 01:36:23 +00:00
$metricTypeIdExecutionsMBSeconds = str_replace ([ '{resourceType}' , '{resourceInternalId}' ], [ $deployment -> getAttribute ( 'resourceType' ), $resource -> getSequence ()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS );
2025-04-02 05:05:24 +00:00
if ( $deployment -> getAttribute ( 'resourceType' ) === 'sites' ) {
$queueForStatsUsage
-> disableMetric ( METRIC_NETWORK_REQUESTS )
-> disableMetric ( METRIC_NETWORK_INBOUND )
-> disableMetric ( METRIC_NETWORK_OUTBOUND );
if ( $resource -> getAttribute ( 'adapter' ) !== 'ssr' ) {
$queueForStatsUsage
-> disableMetric ( METRIC_EXECUTIONS )
-> disableMetric ( METRIC_EXECUTIONS_COMPUTE )
-> disableMetric ( METRIC_EXECUTIONS_MB_SECONDS )
-> disableMetric ( $metricTypeExecutions )
-> disableMetric ( $metricTypeIdExecutions )
-> disableMetric ( $metricTypeExecutionsCompute )
-> disableMetric ( $metricTypeIdExecutionsCompute )
-> disableMetric ( $metricTypeExecutionsMbSeconds )
-> disableMetric ( $metricTypeIdExecutionsMBSeconds );
}
2025-04-02 05:10:12 +00:00
$queueForStatsUsage
-> addMetric ( METRIC_SITES_REQUESTS , 1 )
-> addMetric ( METRIC_SITES_INBOUND , $request -> getSize () + $fileSize )
-> addMetric ( METRIC_SITES_OUTBOUND , $response -> getSize ())
2025-05-27 01:36:23 +00:00
-> addMetric ( str_replace ( '{siteInternalId}' , $resource -> getSequence (), METRIC_SITES_ID_REQUESTS ), 1 )
-> addMetric ( str_replace ( '{siteInternalId}' , $resource -> getSequence (), METRIC_SITES_ID_INBOUND ), $request -> getSize () + $fileSize )
-> addMetric ( str_replace ( '{siteInternalId}' , $resource -> getSequence (), METRIC_SITES_ID_OUTBOUND ), $response -> getSize ())
2025-04-02 05:10:12 +00:00
;
2025-04-02 05:05:24 +00:00
}
$compute = ( int )( $execution -> getAttribute ( 'duration' ) * 1000 );
$mbSeconds = ( int )(( $spec [ 'memory' ] ? ? APP_COMPUTE_MEMORY_DEFAULT ) * $execution -> getAttribute ( 'duration' , 0 ) * ( $spec [ 'cpus' ] ? ? APP_COMPUTE_CPUS_DEFAULT ));
2025-01-30 04:53:53 +00:00
$queueForStatsUsage
2024-11-06 11:29:24 +00:00
-> addMetric ( METRIC_NETWORK_REQUESTS , 1 )
-> addMetric ( METRIC_NETWORK_INBOUND , $request -> getSize () + $fileSize )
-> addMetric ( METRIC_NETWORK_OUTBOUND , $response -> getSize ())
-> addMetric ( METRIC_EXECUTIONS , 1 )
2025-04-02 05:05:24 +00:00
-> addMetric ( $metricTypeExecutions , 1 )
-> addMetric ( $metricTypeIdExecutions , 1 )
-> addMetric ( METRIC_EXECUTIONS_COMPUTE , $compute ) // per project
-> addMetric ( $metricTypeExecutionsCompute , $compute ) // per function
-> addMetric ( $metricTypeIdExecutionsCompute , $compute ) // per function
-> addMetric ( METRIC_EXECUTIONS_MB_SECONDS , $mbSeconds )
-> addMetric ( $metricTypeExecutionsMbSeconds , $mbSeconds )
-> addMetric ( $metricTypeIdExecutionsMBSeconds , $mbSeconds )
2024-11-06 11:29:24 +00:00
-> setProject ( $project )
2025-02-13 18:57:41 +00:00
-> trigger ();
2024-11-06 11:29:24 +00:00
2024-04-22 15:58:40 +00:00
return true ;
} elseif ( $type === 'api' ) {
return false ;
2025-02-23 20:34:14 +00:00
} elseif ( $type === 'redirect' ) {
2025-03-07 17:00:20 +00:00
$url = $rule -> getAttribute ( 'redirectUrl' , '' );
2025-03-07 09:14:45 +00:00
$response -> redirect ( $url , \intval ( $rule -> getAttribute ( 'redirectStatusCode' , 301 )));
2025-02-23 20:34:14 +00:00
return true ;
2024-04-22 15:58:40 +00:00
} else {
2025-04-09 04:43:54 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVER_ERROR , 'Unknown resource type ' . $type , view : $errorView );
2024-04-22 15:58:40 +00:00
}
2023-02-27 12:43:20 +00:00
return false ;
2024-04-22 15:58:40 +00:00
}
2023-02-27 12:43:20 +00:00
2024-04-10 17:46:31 +00:00
App :: init ()
-> groups ([ 'api' ])
-> inject ( 'project' )
-> inject ( 'mode' )
2024-04-10 17:49:16 +00:00
-> action ( function ( Document $project , string $mode ) {
2024-04-10 17:46:31 +00:00
if ( $mode === APP_MODE_ADMIN && $project -> getId () === 'console' ) {
throw new AppwriteException ( AppwriteException :: GENERAL_BAD_REQUEST , 'Admin mode is not allowed for console project' );
}
});
2024-04-11 07:28:16 +00:00
App :: init ()
2025-03-03 15:42:29 +00:00
-> groups ([ 'database' , 'functions' , 'sites' , 'messaging' ])
2024-04-11 07:28:16 +00:00
-> inject ( 'project' )
2024-04-17 13:17:44 +00:00
-> inject ( 'request' )
2024-04-29 08:16:09 +00:00
-> action ( function ( Document $project , Request $request ) {
2024-04-11 07:28:16 +00:00
if ( $project -> getId () === 'console' ) {
2024-04-18 06:26:11 +00:00
$message = empty ( $request -> getHeader ( 'x-appwrite-project' )) ?
2024-04-29 04:52:46 +00:00
'No Appwrite project was specified. Please specify your project ID when initializing your Appwrite SDK.' :
2024-04-18 06:26:11 +00:00
'This endpoint is not available for the console project. The Appwrite Console is a reserved project ID and cannot be used with the Appwrite SDKs and APIs. Please check if your project ID is correct.' ;
2024-04-18 08:37:20 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_ACCESS_FORBIDDEN , $message );
2024-04-11 07:28:16 +00:00
}
});
2022-07-22 06:00:42 +00:00
App :: init ()
2025-02-06 10:08:28 +00:00
-> groups ([ 'api' , 'web' ])
2022-07-22 06:00:42 +00:00
-> inject ( 'utopia' )
2023-02-09 14:27:11 +00:00
-> inject ( 'swooleRequest' )
2022-07-22 06:00:42 +00:00
-> inject ( 'request' )
-> inject ( 'response' )
2025-06-19 14:15:43 +00:00
-> inject ( 'log' )
2022-07-22 06:00:42 +00:00
-> inject ( 'console' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2024-04-22 15:58:40 +00:00
-> inject ( 'getProjectDB' )
2022-07-22 06:00:42 +00:00
-> inject ( 'locale' )
2023-04-17 02:10:17 +00:00
-> inject ( 'localeCodes' )
2025-04-14 11:56:42 +00:00
-> inject ( 'platforms' )
2024-04-22 15:58:40 +00:00
-> inject ( 'geodb' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2024-04-22 15:58:40 +00:00
-> inject ( 'queueForEvents' )
2022-12-20 16:11:30 +00:00
-> inject ( 'queueForCertificates' )
2024-10-09 08:34:08 +00:00
-> inject ( 'queueForFunctions' )
2025-03-25 11:33:03 +00:00
-> inject ( 'executor' )
2024-10-17 11:32:42 +00:00
-> inject ( 'isResourceBlocked' )
2024-11-06 16:05:58 +00:00
-> inject ( 'previewHostname' )
2024-11-22 05:55:39 +00:00
-> inject ( 'devKey' )
2025-02-21 19:50:43 +00:00
-> inject ( 'apiKey' )
2025-06-23 09:28:18 +00:00
-> inject ( 'httpReferrer' )
-> inject ( 'httpReferrerSafe' )
2025-06-30 13:08:25 +00:00
-> action ( function ( App $utopia , SwooleRequest $swooleRequest , Request $request , Response $response , Log $log , Document $console , Document $project , Database $dbForPlatform , callable $getProjectDB , Locale $locale , array $localeCodes , array $platforms , Reader $geodb , StatsUsage $queueForStatsUsage , Event $queueForEvents , Certificate $queueForCertificates , Func $queueForFunctions , Executor $executor , callable $isResourceBlocked , string $previewHostname , Document $devKey , ? Key $apiKey , string $httpReferrer , string $httpReferrerSafe ) {
2023-02-09 14:27:11 +00:00
/*
* Appwrite Router
*/
2023-07-28 07:56:07 +00:00
$host = $request -> getHostname () ? ? '' ;
2024-04-01 11:02:47 +00:00
$mainDomain = System :: getEnv ( '_APP_DOMAIN' , '' );
2023-02-22 15:07:34 +00:00
// Only run Router when external domain
2024-11-06 16:05:58 +00:00
if ( $host !== $mainDomain || ! empty ( $previewHostname )) {
2025-06-19 14:15:43 +00:00
if ( router ( $utopia , $dbForPlatform , $getProjectDB , $swooleRequest , $request , $response , $log , $queueForEvents , $queueForStatsUsage , $queueForFunctions , $executor , $geodb , $isResourceBlocked , $previewHostname , $apiKey )) {
2025-02-04 13:16:17 +00:00
$utopia -> getRoute () ? -> label ( 'router' , true );
2024-04-22 15:58:40 +00:00
}
}
2023-02-21 10:31:55 +00:00
2022-07-22 06:00:42 +00:00
/*
* Request format
*/
2023-02-19 11:04:12 +00:00
$route = $utopia -> getRoute ();
2022-07-22 06:00:42 +00:00
Request :: setRoute ( $route );
2023-02-21 10:31:55 +00:00
if ( $route === null ) {
2023-11-27 02:22:05 +00:00
return $response
-> setStatusCode ( 404 )
-> send ( 'Not Found' );
2023-02-14 13:58:13 +00:00
}
2024-04-01 12:41:17 +00:00
$requestFormat = $request -> getHeader ( 'x-appwrite-response-format' , System :: getEnv ( '_APP_SYSTEM_RESPONSE_FORMAT' , '' ));
2022-07-22 06:00:42 +00:00
if ( $requestFormat ) {
2024-03-07 13:48:36 +00:00
if ( version_compare ( $requestFormat , '1.4.0' , '<' )) {
2024-03-07 14:41:20 +00:00
$request -> addFilter ( new RequestV16 ());
2024-03-07 13:48:36 +00:00
}
if ( version_compare ( $requestFormat , '1.5.0' , '<' )) {
2024-03-07 14:41:20 +00:00
$request -> addFilter ( new RequestV17 ());
2022-07-22 06:00:42 +00:00
}
2024-07-17 05:52:17 +00:00
if ( version_compare ( $requestFormat , '1.6.0' , '<' )) {
$request -> addFilter ( new RequestV18 ());
}
2025-03-12 11:10:10 +00:00
if ( version_compare ( $requestFormat , '1.7.0' , '<' )) {
$request -> addFilter ( new RequestV19 ());
}
2022-07-22 06:00:42 +00:00
}
2021-05-11 10:47:02 +00:00
2022-07-22 06:00:42 +00:00
$domain = $request -> getHostname ();
$domains = Config :: getParam ( 'domains' , []);
if ( ! array_key_exists ( $domain , $domains )) {
$domain = new Domain ( ! empty ( $domain ) ? $domain : '' );
2021-07-15 21:14:52 +00:00
2022-07-22 06:00:42 +00:00
if ( empty ( $domain -> get ()) || ! $domain -> isKnown () || $domain -> isTest ()) {
$domains [ $domain -> get ()] = false ;
Console :: warning ( $domain -> get () . ' is not a publicly accessible domain. Skipping SSL certificate generation.' );
} elseif ( str_starts_with ( $request -> getURI (), '/.well-known/acme-challenge' )) {
Console :: warning ( 'Skipping SSL certificates generation on ACME challenge.' );
2022-03-29 10:17:56 +00:00
} else {
2022-07-22 06:00:42 +00:00
Authorization :: disable ();
2024-04-01 11:02:47 +00:00
$envDomain = System :: getEnv ( '_APP_DOMAIN' , '' );
2022-07-22 06:00:42 +00:00
$mainDomain = null ;
if ( ! empty ( $envDomain ) && $envDomain !== 'localhost' ) {
$mainDomain = $envDomain ;
} else {
2024-11-22 17:16:51 +00:00
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
2024-11-28 10:33:00 +00:00
if ( System :: getEnv ( '_APP_RULES_FORMAT' ) === 'md5' ) {
2024-12-12 10:30:26 +00:00
$domainDocument = $dbForPlatform -> getDocument ( 'rules' , md5 ( $envDomain ));
2024-11-28 10:08:24 +00:00
} else {
2024-12-12 10:30:26 +00:00
$domainDocument = $dbForPlatform -> findOne ( 'rules' , [ Query :: orderAsc ( '$id' )]);
2024-11-22 17:16:51 +00:00
}
2024-10-07 02:40:01 +00:00
$mainDomain = ! $domainDocument -> isEmpty () ? $domainDocument -> getAttribute ( 'domain' ) : $domain -> get ();
2022-07-22 06:00:42 +00:00
}
2021-12-28 11:02:39 +00:00
2022-07-22 06:00:42 +00:00
if ( $mainDomain !== $domain -> get ()) {
Console :: warning ( $domain -> get () . ' is not a main domain. Skipping SSL certificate generation.' );
} else {
2024-11-22 17:16:51 +00:00
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
2024-11-28 10:33:00 +00:00
if ( System :: getEnv ( '_APP_RULES_FORMAT' ) === 'md5' ) {
2024-12-12 10:30:26 +00:00
$domainDocument = $dbForPlatform -> getDocument ( 'rules' , md5 ( $domain -> get ()));
2024-11-28 10:08:24 +00:00
} else {
2024-12-12 10:30:26 +00:00
$domainDocument = $dbForPlatform -> findOne ( 'rules' , [
2024-11-22 17:16:51 +00:00
Query :: equal ( 'domain' , [ $domain -> get ()])
]);
}
2025-02-26 15:41:44 +00:00
2025-03-14 08:37:44 +00:00
$owner = '' ;
2025-04-25 10:18:04 +00:00
$functionsDomainFallback = System :: getEnv ( '_APP_DOMAIN_FUNCTIONS_FALLBACK' , '' );
2025-03-14 08:37:44 +00:00
$functionsDomain = System :: getEnv ( '_APP_DOMAIN_FUNCTIONS' , '' );
2025-04-25 10:18:04 +00:00
$siteDomain = System :: getEnv ( '_APP_DOMAIN_SITES' , '' );
if ( ! empty ( $functionsDomainFallback ) && \str_ends_with ( $host , $functionsDomainFallback )) {
$functionsDomain = $functionsDomainFallback ;
}
2025-04-25 10:57:45 +00:00
2025-04-25 10:18:04 +00:00
if (
( ! empty ( $functionsDomain ) && \str_ends_with ( $domain -> get (), $functionsDomain )) ||
( ! empty ( $siteDomain ) && \str_ends_with ( $domain -> get (), $siteDomain ))
) {
2025-03-14 08:37:44 +00:00
$owner = 'Appwrite' ;
}
2024-10-07 02:40:01 +00:00
if ( $domainDocument -> isEmpty ()) {
2025-02-25 09:47:47 +00:00
$ruleId = System :: getEnv ( '_APP_RULES_FORMAT' ) === 'md5' ? md5 ( $domain -> get ()) : ID :: unique ();
2022-07-22 06:00:42 +00:00
$domainDocument = new Document ([
2024-11-22 17:16:51 +00:00
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
2025-02-25 09:47:47 +00:00
'$id' => $ruleId ,
2022-07-22 06:00:42 +00:00
'domain' => $domain -> get (),
2025-02-27 09:55:49 +00:00
'type' => 'api' ,
2023-09-05 15:09:54 +00:00
'status' => 'verifying' ,
2025-03-14 11:07:38 +00:00
'projectId' => $console -> getId (),
2025-05-26 05:42:11 +00:00
'projectInternalId' => $console -> getSequence (),
2025-02-25 09:47:47 +00:00
'search' => implode ( ' ' , [ $ruleId , $domain -> get ()]),
2025-03-14 08:37:44 +00:00
'owner' => $owner ,
2025-03-14 11:07:38 +00:00
'region' => $console -> getAttribute ( 'region' )
2022-07-22 06:00:42 +00:00
]);
2025-02-26 15:41:44 +00:00
2024-12-12 10:30:26 +00:00
$domainDocument = $dbForPlatform -> createDocument ( 'rules' , $domainDocument );
2022-05-11 14:12:38 +00:00
2022-07-22 06:00:42 +00:00
Console :: info ( 'Issuing a TLS certificate for the main domain (' . $domain -> get () . ') in a few seconds...' );
2022-04-26 11:15:00 +00:00
2022-12-13 11:16:12 +00:00
$queueForCertificates
2022-07-22 06:00:42 +00:00
-> setDomain ( $domainDocument )
2023-09-05 15:09:54 +00:00
-> setSkipRenewCheck ( true )
2022-07-22 06:00:42 +00:00
-> trigger ();
}
2022-05-12 10:56:25 +00:00
}
2022-07-22 06:00:42 +00:00
$domains [ $domain -> get ()] = true ;
Authorization :: reset (); // ensure authorization is re-enabled
2021-05-11 10:47:02 +00:00
}
2022-07-22 06:00:42 +00:00
Config :: setParam ( 'domains' , $domains );
}
2022-05-12 14:01:01 +00:00
2022-07-22 06:00:42 +00:00
$localeParam = ( string ) $request -> getParam ( 'locale' , $request -> getHeader ( 'x-appwrite-locale' , '' ));
2023-04-17 02:10:17 +00:00
if ( \in_array ( $localeParam , $localeCodes )) {
2022-07-22 06:00:42 +00:00
$locale -> setDefault ( $localeParam );
2021-05-11 10:47:02 +00:00
}
2022-07-22 06:00:42 +00:00
2025-06-23 09:28:18 +00:00
$origin = \parse_url ( $request -> getOrigin ( $httpReferrer ), PHP_URL_HOST );
2022-07-22 06:00:42 +00:00
$selfDomain = new Domain ( $request -> getHostname ());
$endDomain = new Domain (( string ) $origin );
Config :: setParam (
'domainVerification' ,
( $selfDomain -> getRegisterable () === $endDomain -> getRegisterable ()) &&
2023-08-06 13:11:30 +00:00
$endDomain -> getRegisterable () !== ''
2022-07-22 06:00:42 +00:00
);
2023-07-21 10:08:34 +00:00
$isLocalHost = $request -> getHostname () === 'localhost' || $request -> getHostname () === 'localhost:' . $request -> getPort ();
$isIpAddress = filter_var ( $request -> getHostname (), FILTER_VALIDATE_IP ) !== false ;
$isConsoleProject = $project -> getAttribute ( '$id' , '' ) === 'console' ;
2024-04-01 11:02:47 +00:00
$isConsoleRootSession = System :: getEnv ( '_APP_CONSOLE_ROOT_SESSION' , 'disabled' ) === 'enabled' ;
2023-07-21 10:08:34 +00:00
Config :: setParam (
'cookieDomain' ,
$isLocalHost || $isIpAddress
? null
2024-03-06 17:34:21 +00:00
: (
2024-06-07 00:54:51 +00:00
$isConsoleProject && $isConsoleRootSession
2023-07-21 10:08:34 +00:00
? '.' . $selfDomain -> getRegisterable ()
: '.' . $request -> getHostname ()
2024-06-07 00:54:51 +00:00
)
2023-07-21 10:08:34 +00:00
);
2022-07-22 06:00:42 +00:00
/*
* Response format
*/
2024-04-01 12:41:17 +00:00
$responseFormat = $request -> getHeader ( 'x-appwrite-response-format' , System :: getEnv ( '_APP_SYSTEM_RESPONSE_FORMAT' , '' ));
2022-07-22 06:00:42 +00:00
if ( $responseFormat ) {
2024-03-07 13:48:36 +00:00
if ( version_compare ( $responseFormat , '1.4.0' , '<' )) {
2024-03-07 14:41:20 +00:00
$response -> addFilter ( new ResponseV16 ());
2024-03-07 13:48:36 +00:00
}
if ( version_compare ( $responseFormat , '1.5.0' , '<' )) {
2024-03-07 14:41:20 +00:00
$response -> addFilter ( new ResponseV17 ());
2019-10-24 17:53:37 +00:00
}
2024-07-17 05:52:17 +00:00
if ( version_compare ( $responseFormat , '1.6.0' , '<' )) {
$response -> addFilter ( new ResponseV18 ());
}
2025-03-12 11:10:10 +00:00
if ( version_compare ( $responseFormat , '1.7.0' , '<' )) {
$response -> addFilter ( new ResponseV19 ());
}
2024-05-24 15:27:13 +00:00
if ( version_compare ( $responseFormat , APP_VERSION_STABLE , '>' )) {
2025-02-13 18:57:41 +00:00
$response -> addHeader ( 'X-Appwrite-Warning' , " The current SDK is built for Appwrite " . $responseFormat . " . However, the current Appwrite server version is " . APP_VERSION_STABLE . " . Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks " );
2024-05-15 09:46:31 +00:00
}
2019-10-24 17:53:37 +00:00
}
2021-08-18 10:20:49 +00:00
2020-12-28 17:03:47 +00:00
/*
2022-07-22 06:00:42 +00:00
* Security Headers
*
* As recommended at :
* @ see https :// www . owasp . org / index . php / List_of_useful_HTTP_headers
*/
2024-04-01 11:02:47 +00:00
if ( System :: getEnv ( '_APP_OPTIONS_FORCE_HTTPS' , 'disabled' ) === 'enabled' ) { // Force HTTPS
2023-10-31 18:31:36 +00:00
if ( $request -> getProtocol () !== 'https' && ( $swooleRequest -> header [ 'host' ] ? ? '' ) !== 'localhost' && ( $swooleRequest -> header [ 'host' ] ? ? '' ) !== APP_HOSTNAME_INTERNAL ) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
2022-07-22 06:00:42 +00:00
if ( $request -> getMethod () !== Request :: METHOD_GET ) {
2023-10-02 14:02:48 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_PROTOCOL_UNSUPPORTED , 'Method unsupported over HTTP. Please use HTTPS instead.' );
2022-07-22 06:00:42 +00:00
}
return $response -> redirect ( 'https://' . $request -> getHostname () . $request -> getURI ());
2022-06-03 09:12:19 +00:00
}
2022-11-21 03:49:45 +00:00
}
2022-05-31 15:41:12 +00:00
2022-11-21 03:49:45 +00:00
if ( $request -> getProtocol () === 'https' ) {
2022-07-22 06:00:42 +00:00
$response -> addHeader ( 'Strict-Transport-Security' , 'max-age=' . ( 60 * 60 * 24 * 126 )); // 126 days
2020-12-28 17:03:47 +00:00
}
2024-12-01 17:25:43 +00:00
2022-07-22 06:00:42 +00:00
$response
-> addHeader ( 'Server' , 'Appwrite' )
-> addHeader ( 'X-Content-Type-Options' , 'nosniff' )
-> addHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, PATCH, DELETE' )
2025-05-21 10:32:56 +00:00
-> addHeader ( 'Access-Control-Allow-Headers' , 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent' )
2023-12-12 15:39:24 +00:00
-> addHeader ( 'Access-Control-Expose-Headers' , 'X-Appwrite-Session, X-Fallback-Cookies' )
2025-06-23 09:28:18 +00:00
-> addHeader ( 'Access-Control-Allow-Origin' , $httpReferrerSafe )
2023-08-06 13:11:30 +00:00
-> addHeader ( 'Access-Control-Allow-Credentials' , 'true' );
2019-05-09 06:54:39 +00:00
2025-04-15 12:02:55 +00:00
if ( ! $devKey -> isEmpty ()) {
$response -> addHeader ( 'Access-Control-Allow-Origin' , '*' );
}
2022-07-22 06:00:42 +00:00
/*
* Validate Client Domain - Check to avoid CSRF attack
* Adding Appwrite API domains to allow XDOMAIN communication
* Skip this check for non - web platforms which are not required to send an origin header
*/
$origin = $request -> getOrigin ( $request -> getReferer ( '' ));
2025-04-14 11:56:42 +00:00
$originValidator = new Origin ( $platforms );
2019-05-09 06:54:39 +00:00
2022-05-31 11:35:59 +00:00
if (
2025-06-30 13:37:49 +00:00
$devKey -> isEmpty ()
2025-06-30 14:18:05 +00:00
&& ! empty ( $origin )
2025-06-30 13:37:49 +00:00
&& ! $originValidator -> isValid ( $origin )
2022-07-22 06:00:42 +00:00
&& \in_array ( $request -> getMethod (), [ Request :: METHOD_POST , Request :: METHOD_PUT , Request :: METHOD_PATCH , Request :: METHOD_DELETE ])
&& $route -> getLabel ( 'origin' , false ) !== '*'
&& empty ( $request -> getHeader ( 'x-appwrite-key' , '' ))
2025-06-23 09:28:18 +00:00
&& \parse_url ( $httpReferrerSafe , PHP_URL_HOST ) === 'localhost'
2022-05-31 11:35:59 +00:00
) {
2022-08-14 06:56:12 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_UNKNOWN_ORIGIN , $originValidator -> getDescription ());
2021-07-29 10:28:17 +00:00
}
2022-07-22 06:00:42 +00:00
});
2022-03-01 14:19:47 +00:00
2022-07-22 06:00:42 +00:00
App :: options ()
2023-07-11 08:53:40 +00:00
-> inject ( 'utopia' )
2023-02-27 12:43:20 +00:00
-> inject ( 'swooleRequest' )
2024-04-22 15:58:40 +00:00
-> inject ( 'request' )
-> inject ( 'response' )
2025-06-26 07:04:38 +00:00
-> inject ( 'log' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2024-04-22 15:58:40 +00:00
-> inject ( 'getProjectDB' )
-> inject ( 'queueForEvents' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2024-10-09 08:34:08 +00:00
-> inject ( 'queueForFunctions' )
2025-03-25 11:33:03 +00:00
-> inject ( 'executor' )
2024-04-22 15:58:40 +00:00
-> inject ( 'geodb' )
2024-10-17 11:32:42 +00:00
-> inject ( 'isResourceBlocked' )
2024-11-06 16:05:58 +00:00
-> inject ( 'previewHostname' )
2025-02-23 06:04:28 +00:00
-> inject ( 'project' )
2024-11-22 05:55:39 +00:00
-> inject ( 'devKey' )
2025-02-21 19:50:43 +00:00
-> inject ( 'apiKey' )
2025-06-26 07:37:11 +00:00
-> action ( function ( App $utopia , SwooleRequest $swooleRequest , Request $request , Response $response , Log $log , Database $dbForPlatform , callable $getProjectDB , Event $queueForEvents , StatsUsage $queueForStatsUsage , Func $queueForFunctions , Executor $executor , Reader $geodb , callable $isResourceBlocked , string $previewHostname , Document $project , Document $devKey , ? Key $apiKey ) {
2024-04-22 15:58:40 +00:00
/*
* Appwrite Router
*/
$host = $request -> getHostname () ? ? '' ;
$mainDomain = System :: getEnv ( '_APP_DOMAIN' , '' );
// Only run Router when external domain
2024-11-06 16:05:58 +00:00
if ( $host !== $mainDomain || ! empty ( $previewHostname )) {
2025-06-19 14:15:43 +00:00
if ( router ( $utopia , $dbForPlatform , $getProjectDB , $swooleRequest , $request , $response , $log , $queueForEvents , $queueForStatsUsage , $queueForFunctions , $executor , $geodb , $isResourceBlocked , $previewHostname , $apiKey )) {
2025-02-04 13:16:17 +00:00
$utopia -> getRoute () ? -> label ( 'router' , true );
2024-04-22 15:58:40 +00:00
}
}
$origin = $request -> getOrigin ();
$response
-> addHeader ( 'Server' , 'Appwrite' )
-> addHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, PATCH, DELETE' )
2025-05-21 10:32:56 +00:00
-> addHeader ( 'Access-Control-Allow-Headers' , 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent' )
2024-04-22 15:58:40 +00:00
-> addHeader ( 'Access-Control-Expose-Headers' , 'X-Appwrite-Session, X-Fallback-Cookies' )
-> addHeader ( 'Access-Control-Allow-Origin' , $origin )
-> addHeader ( 'Access-Control-Allow-Credentials' , 'true' )
-> noContent ();
2025-02-23 06:04:28 +00:00
2025-04-15 12:02:55 +00:00
if ( ! $devKey -> isEmpty ()) {
$response -> addHeader ( 'Access-Control-Allow-Origin' , '*' );
}
2025-02-23 06:04:28 +00:00
/** OPTIONS requests in utopia do not execute shutdown handlers , as a result we need to track the OPTIONS requests explicitly
* @ see https :// github . com / utopia - php / http / blob / 0.33 . 16 / src / App . php #L825-L855
*/
$queueForStatsUsage
-> addMetric ( METRIC_NETWORK_REQUESTS , 1 )
-> addMetric ( METRIC_NETWORK_INBOUND , $request -> getSize ())
-> addMetric ( METRIC_NETWORK_OUTBOUND , $response -> getSize ())
-> setProject ( $project )
-> trigger ();
2024-04-22 15:58:40 +00:00
});
2022-07-22 06:00:42 +00:00
App :: error ()
-> inject ( 'error' )
-> inject ( 'utopia' )
2024-04-15 05:29:32 +00:00
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'logger' )
-> inject ( 'log' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2024-11-22 04:21:03 +00:00
-> inject ( 'devKey' )
2025-01-30 04:53:53 +00:00
-> action ( function ( Throwable $error , App $utopia , Request $request , Response $response , Document $project , ? Logger $logger , Log $log , StatsUsage $queueForStatsUsage ) {
2024-04-15 05:29:32 +00:00
$version = System :: getEnv ( '_APP_VERSION' , 'UNKNOWN' );
2023-02-19 11:04:12 +00:00
$route = $utopia -> getRoute ();
2024-03-13 11:41:55 +00:00
$class = \get_class ( $error );
$code = $error -> getCode ();
$message = $error -> getMessage ();
$file = $error -> getFile ();
$line = $error -> getLine ();
$trace = $error -> getTrace ();
2025-03-18 17:29:40 +00:00
2024-03-13 11:41:55 +00:00
if ( php_sapi_name () === 'cli' ) {
Console :: error ( '[Error] Timestamp: ' . date ( 'c' , time ()));
if ( $route ) {
Console :: error ( '[Error] Method: ' . $route -> getMethod ());
Console :: error ( '[Error] URL: ' . $route -> getPath ());
}
Console :: error ( '[Error] Type: ' . get_class ( $error ));
Console :: error ( '[Error] Message: ' . $message );
Console :: error ( '[Error] File: ' . $file );
Console :: error ( '[Error] Line: ' . $line );
}
switch ( $class ) {
case 'Utopia\Exception' :
$error = new AppwriteException ( AppwriteException :: GENERAL_UNKNOWN , $message , $code , $error );
switch ( $code ) {
case 400 :
$error -> setType ( AppwriteException :: GENERAL_ARGUMENT_INVALID );
break ;
case 404 :
$error -> setType ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND );
break ;
}
break ;
case 'Utopia\Database\Exception\Authorization' :
$error = new AppwriteException ( AppwriteException :: USER_UNAUTHORIZED );
break ;
2025-05-15 11:33:00 +00:00
case 'Utopia\Database\Exception\Timeout' :
$error = new AppwriteException ( AppwriteException :: DATABASE_TIMEOUT , previous : $error );
2025-01-15 09:16:10 +00:00
break ;
2024-03-13 11:41:55 +00:00
}
$code = $error -> getCode ();
$message = $error -> getMessage ();
2023-11-16 18:21:09 +00:00
2024-04-15 05:29:32 +00:00
if ( $error instanceof AppwriteException ) {
$publish = $error -> isPublishable ();
} else {
$publish = $error -> getCode () === 0 || $error -> getCode () >= 500 ;
}
2022-07-22 06:00:42 +00:00
2025-03-07 20:59:11 +00:00
$providerConfig = System :: getEnv ( '_APP_EXPERIMENT_LOGGING_CONFIG' , '' );
if ( ! empty ( $providerConfig ) && $error -> getCode () >= 400 && $error -> getCode () < 500 ) {
2024-09-05 22:14:06 +00:00
// Register error logger
2024-08-26 11:05:50 +00:00
try {
2025-03-22 08:17:09 +00:00
$loggingProvider = new DSN ( $providerConfig );
2024-08-26 10:42:48 +00:00
$providerName = $loggingProvider -> getScheme ();
2024-08-26 11:05:50 +00:00
2024-08-26 10:42:48 +00:00
if ( ! empty ( $providerName ) && $providerName === 'sentry' ) {
$key = $loggingProvider -> getPassword ();
$projectId = $loggingProvider -> getUser () ? ? '' ;
$host = 'https://' . $loggingProvider -> getHost ();
2025-03-22 08:17:09 +00:00
$sampleRate = $loggingProvider -> getParam ( 'sample' , 0.01 );
2024-08-26 10:42:48 +00:00
$adapter = new Sentry ( $projectId , $key , $host );
$logger = new Logger ( $adapter );
2025-03-22 08:17:09 +00:00
$logger -> setSample ( $sampleRate );
2024-08-26 10:42:48 +00:00
$publish = true ;
} else {
throw new \Exception ( 'Invalid experimental logging provider' );
2024-09-05 22:14:06 +00:00
}
2024-08-26 10:42:48 +00:00
} catch ( \Throwable $th ) {
Console :: warning ( 'Failed to initialize logging provider: ' . $th -> getMessage ());
2024-09-05 22:14:06 +00:00
}
}
2024-04-15 05:29:32 +00:00
2025-02-23 06:07:54 +00:00
/**
* If its not a publishable error , track usage stats . Publishable errors are >= 500 or those explicitly marked as publish = true in errors . php
*/
if ( ! $publish && $project -> getId () !== 'console' ) {
2024-05-23 13:39:23 +00:00
if ( ! Auth :: isPrivilegedUser ( Authorization :: getRoles ())) {
$fileSize = 0 ;
$file = $request -> getFiles ( 'file' );
if ( ! empty ( $file )) {
$fileSize = ( \is_array ( $file [ 'size' ]) && isset ( $file [ 'size' ][ 0 ])) ? $file [ 'size' ][ 0 ] : $file [ 'size' ];
}
2025-01-30 04:53:53 +00:00
$queueForStatsUsage
2024-05-23 13:39:23 +00:00
-> addMetric ( METRIC_NETWORK_REQUESTS , 1 )
-> addMetric ( METRIC_NETWORK_INBOUND , $request -> getSize () + $fileSize )
-> addMetric ( METRIC_NETWORK_OUTBOUND , $response -> getSize ());
}
2025-01-30 04:53:53 +00:00
$queueForStatsUsage
2024-05-23 13:39:23 +00:00
-> setProject ( $project )
-> trigger ();
}
2024-03-11 11:54:42 +00:00
if ( $logger && $publish ) {
2024-01-23 15:40:32 +00:00
try {
/** @var Utopia\Database\Document $user */
$user = $utopia -> getResource ( 'user' );
2024-03-13 11:41:55 +00:00
} catch ( \Throwable ) {
2024-01-23 15:40:32 +00:00
// All good, user is optional information for logger
}
2022-07-22 06:00:42 +00:00
2024-04-15 05:29:32 +00:00
if ( isset ( $user ) && ! $user -> isEmpty ()) {
$log -> setUser ( new User ( $user -> getId ()));
2024-11-26 13:54:27 +00:00
} else {
$log -> setUser ( new User ( 'guest-' . hash ( 'sha256' , $request -> getIP ())));
2024-04-15 05:29:32 +00:00
}
2024-05-08 04:25:12 +00:00
try {
$dsn = new DSN ( $project -> getAttribute ( 'database' , 'console' ));
} catch ( \InvalidArgumentException ) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN ( 'mysql://' . $project -> getAttribute ( 'database' , 'console' ));
}
2024-04-15 05:29:32 +00:00
$log -> setNamespace ( " http " );
2024-11-26 13:54:27 +00:00
$log -> setServer ( System :: getEnv ( '_APP_LOGGING_SERVICE_IDENTIFIER' , \gethostname ()));
2024-04-15 05:29:32 +00:00
$log -> setVersion ( $version );
$log -> setType ( Log :: TYPE_ERROR );
$log -> setMessage ( $error -> getMessage ());
2024-05-08 04:25:12 +00:00
$log -> addTag ( 'database' , $dsn -> getHost ());
2024-04-15 05:29:32 +00:00
$log -> addTag ( 'method' , $route -> getMethod ());
2025-01-14 01:50:40 +00:00
$log -> addTag ( 'url' , $request -> getURI ());
2024-04-15 05:29:32 +00:00
$log -> addTag ( 'verboseType' , get_class ( $error ));
$log -> addTag ( 'code' , $error -> getCode ());
2025-06-19 14:15:43 +00:00
$tags = $log -> getTags ();
2025-06-23 16:50:59 +00:00
if ( ! isset ( $tags [ 'projectId' ])) {
2025-06-19 14:15:43 +00:00
$log -> addTag ( 'projectId' , $project -> getId ());
}
2024-04-15 05:29:32 +00:00
$log -> addTag ( 'hostname' , $request -> getHostname ());
$log -> addTag ( 'locale' , ( string ) $request -> getParam ( 'locale' , $request -> getHeader ( 'x-appwrite-locale' , '' )));
$log -> addExtra ( 'file' , $error -> getFile ());
$log -> addExtra ( 'line' , $error -> getLine ());
$log -> addExtra ( 'trace' , $error -> getTraceAsString ());
2024-01-23 15:40:32 +00:00
$log -> addExtra ( 'roles' , Authorization :: getRoles ());
2024-04-15 05:29:32 +00:00
2025-01-17 04:38:38 +00:00
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD' ;
if ( ! empty ( $sdk )) {
/** @var Appwrite\SDK\Method $sdk */
$action = $sdk -> getNamespace () . '.' . $sdk -> getMethodName ();
}
2024-04-15 05:29:32 +00:00
$log -> setAction ( $action );
2024-11-26 13:54:27 +00:00
$log -> addTag ( 'service' , $action );
2024-04-15 05:29:32 +00:00
$isProduction = System :: getEnv ( '_APP_ENV' , 'development' ) === 'production' ;
$log -> setEnvironment ( $isProduction ? Log :: ENVIRONMENT_PRODUCTION : Log :: ENVIRONMENT_STAGING );
2024-09-26 10:44:07 +00:00
try {
$responseCode = $logger -> addLog ( $log );
2024-09-26 10:51:51 +00:00
Console :: info ( 'Error log pushed with status code: ' . $responseCode );
2024-09-26 10:44:07 +00:00
} catch ( Throwable $th ) {
2024-09-26 10:51:51 +00:00
Console :: error ( 'Error pushing log: ' . $th -> getMessage ());
2024-09-26 10:44:07 +00:00
}
2024-04-15 05:29:32 +00:00
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if ( ! ( $error instanceof AppwriteException )) {
2022-08-08 14:44:07 +00:00
$error = new AppwriteException ( AppwriteException :: GENERAL_UNKNOWN , $message , $code , $error );
2024-04-15 05:29:32 +00:00
}
2024-04-15 04:29:12 +00:00
2024-04-15 05:29:32 +00:00
switch ( $code ) { // Don't show 500 errors!
case 400 : // Error allowed publicly
case 401 : // Error allowed publicly
case 402 : // Error allowed publicly
case 403 : // Error allowed publicly
case 404 : // Error allowed publicly
case 408 : // Error allowed publicly
case 409 : // Error allowed publicly
case 412 : // Error allowed publicly
case 416 : // Error allowed publicly
case 429 : // Error allowed publicly
case 451 : // Error allowed publicly
case 501 : // Error allowed publicly
case 503 : // Error allowed publicly
break ;
default :
$code = 500 ; // All other errors get the generic 500 server error status code
2022-07-22 06:00:42 +00:00
$message = 'Server Error' ;
2024-04-15 05:29:32 +00:00
}
2024-04-15 04:29:12 +00:00
2024-04-15 05:29:32 +00:00
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
$type = $error -> getType ();
2025-03-24 05:06:18 +00:00
$output = App :: isDevelopment () ? [
2024-04-15 05:29:32 +00:00
'message' => $message ,
'code' => $code ,
'file' => $file ,
'line' => $line ,
'trace' => \json_encode ( $trace , JSON_UNESCAPED_UNICODE ) === false ? [] : $trace , // check for failing encode
2024-04-29 22:50:20 +00:00
'version' => APP_VERSION_STABLE ,
2024-04-15 05:29:32 +00:00
'type' => $type ,
] : [
'message' => $message ,
'code' => $code ,
2024-04-29 22:50:20 +00:00
'version' => APP_VERSION_STABLE ,
2024-04-15 05:29:32 +00:00
'type' => $type ,
];
2024-04-15 04:29:12 +00:00
2024-04-15 05:29:32 +00:00
$response
-> addHeader ( 'Cache-Control' , 'no-cache, no-store, must-revalidate' )
-> addHeader ( 'Expires' , '0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> setStatusCode ( $code );
2025-04-15 09:40:32 +00:00
$template = $error -> getView () ? ? (( $route ) ? $route -> getLabel ( 'error' , null ) : null );
2024-04-15 05:29:32 +00:00
2025-04-15 09:40:32 +00:00
// TODO: Ideally use group 'api' here, but all wildcard routes seem to have 'api' at the moment
2025-04-15 10:48:25 +00:00
if ( ! \str_starts_with ( $route -> getPath (), '/v1' )) {
2025-04-15 09:40:32 +00:00
$template = __DIR__ . '/../views/general/error.phtml' ;
}
2024-04-15 05:29:32 +00:00
2025-04-15 09:40:32 +00:00
if ( ! empty ( $template )) {
2024-04-15 05:29:32 +00:00
$layout = new View ( $template );
$layout
-> setParam ( 'title' , $project -> getAttribute ( 'name' ) . ' - Error' )
2022-07-22 06:00:42 +00:00
-> setParam ( 'development' , App :: isDevelopment ())
2024-04-15 05:29:32 +00:00
-> setParam ( 'projectName' , $project -> getAttribute ( 'name' ))
-> setParam ( 'projectURL' , $project -> getAttribute ( 'url' ))
-> setParam ( 'message' , $output [ 'message' ] ? ? '' )
-> setParam ( 'type' , $output [ 'type' ] ? ? '' )
-> setParam ( 'code' , $output [ 'code' ] ? ? '' )
2025-04-09 04:43:54 +00:00
-> setParam ( 'trace' , $output [ 'trace' ] ? ? [])
-> setParam ( 'exception' , $error );
2024-04-15 05:29:32 +00:00
$response -> html ( $layout -> render ());
2025-02-08 11:11:09 +00:00
return ;
2024-04-15 05:29:32 +00:00
}
2024-04-15 04:29:12 +00:00
2024-04-15 05:29:32 +00:00
$response -> dynamic (
new Document ( $output ),
2022-07-22 06:00:42 +00:00
$utopia -> isDevelopment () ? Response :: MODEL_ERROR_DEV : Response :: MODEL_ERROR
2024-04-15 05:29:32 +00:00
);
});
2019-10-24 17:53:37 +00:00
2020-06-28 17:31:21 +00:00
App :: get ( '/robots.txt' )
2019-10-24 17:53:37 +00:00
-> desc ( 'Robots.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
2024-05-23 09:17:59 +00:00
-> inject ( 'utopia' )
-> inject ( 'swooleRequest' )
-> inject ( 'request' )
2020-12-26 12:19:46 +00:00
-> inject ( 'response' )
2025-06-26 07:04:38 +00:00
-> inject ( 'log' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2024-05-23 09:17:59 +00:00
-> inject ( 'getProjectDB' )
-> inject ( 'queueForEvents' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2024-10-09 08:34:08 +00:00
-> inject ( 'queueForFunctions' )
2025-03-25 11:33:03 +00:00
-> inject ( 'executor' )
2024-05-23 09:17:59 +00:00
-> inject ( 'geodb' )
2024-10-17 11:32:42 +00:00
-> inject ( 'isResourceBlocked' )
2024-11-06 16:05:58 +00:00
-> inject ( 'previewHostname' )
2025-02-21 19:50:43 +00:00
-> inject ( 'apiKey' )
2025-06-26 16:49:52 +00:00
-> action ( function ( App $utopia , SwooleRequest $swooleRequest , Request $request , Response $response , Log $log , Database $dbForPlatform , callable $getProjectDB , Event $queueForEvents , StatsUsage $queueForStatsUsage , Func $queueForFunctions , Executor $executor , Reader $geodb , callable $isResourceBlocked , string $previewHostname , ? Key $apiKey ) {
2024-05-23 09:17:59 +00:00
$host = $request -> getHostname () ? ? '' ;
2025-08-01 03:58:07 +00:00
$consoleDomain = System :: getEnv ( '_APP_CONSOLE_DOMAIN' , '' );
$mainDomain = System :: getEnv ( '_APP_DOMAIN' , '' );
2024-05-23 09:17:59 +00:00
2025-08-01 03:58:07 +00:00
if (( $host === $consoleDomain || $host === $mainDomain || $host === 'localhost' ) && empty ( $previewHostname )) {
2024-05-23 09:17:59 +00:00
$template = new View ( __DIR__ . '/../views/general/robots.phtml' );
$response -> text ( $template -> render ( false ));
} else {
2025-06-19 14:15:43 +00:00
if ( router ( $utopia , $dbForPlatform , $getProjectDB , $swooleRequest , $request , $response , $log , $queueForEvents , $queueForStatsUsage , $queueForFunctions , $executor , $geodb , $isResourceBlocked , $previewHostname , $apiKey )) {
2025-02-04 13:16:17 +00:00
$utopia -> getRoute () ? -> label ( 'router' , true );
2024-11-20 12:48:42 +00:00
}
2024-05-23 09:17:59 +00:00
}
2020-12-26 12:19:46 +00:00
});
2019-10-24 17:53:37 +00:00
2020-06-28 17:31:21 +00:00
App :: get ( '/humans.txt' )
2019-10-24 17:53:37 +00:00
-> desc ( 'Humans.txt File' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
2024-05-23 10:24:12 +00:00
-> inject ( 'utopia' )
-> inject ( 'swooleRequest' )
-> inject ( 'request' )
2020-12-26 12:19:46 +00:00
-> inject ( 'response' )
2025-06-26 07:04:38 +00:00
-> inject ( 'log' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2024-05-23 10:24:12 +00:00
-> inject ( 'getProjectDB' )
-> inject ( 'queueForEvents' )
2025-01-30 04:53:53 +00:00
-> inject ( 'queueForStatsUsage' )
2024-10-09 08:34:08 +00:00
-> inject ( 'queueForFunctions' )
2025-03-25 11:33:03 +00:00
-> inject ( 'executor' )
2024-05-23 10:24:12 +00:00
-> inject ( 'geodb' )
2024-10-17 11:32:42 +00:00
-> inject ( 'isResourceBlocked' )
2024-11-06 16:05:58 +00:00
-> inject ( 'previewHostname' )
2025-02-21 19:50:43 +00:00
-> inject ( 'apiKey' )
2025-06-26 07:04:38 +00:00
-> action ( function ( App $utopia , SwooleRequest $swooleRequest , Request $request , Response $response , Log $log , Database $dbForPlatform , callable $getProjectDB , Event $queueForEvents , StatsUsage $queueForStatsUsage , Func $queueForFunctions , Executor $executor , Reader $geodb , callable $isResourceBlocked , string $previewHostname , ? Key $apiKey ) {
2024-05-23 10:24:12 +00:00
$host = $request -> getHostname () ? ? '' ;
2025-08-01 03:58:07 +00:00
$consoleDomain = System :: getEnv ( '_APP_CONSOLE_DOMAIN' , '' );
$mainDomain = System :: getEnv ( '_APP_DOMAIN' , '' );
2024-05-23 10:24:12 +00:00
2025-08-01 03:58:07 +00:00
if (( $host === $consoleDomain || $host === $mainDomain || $host === 'localhost' ) && empty ( $previewHostname )) {
2024-05-23 10:24:12 +00:00
$template = new View ( __DIR__ . '/../views/general/humans.phtml' );
$response -> text ( $template -> render ( false ));
} else {
2025-06-19 14:15:43 +00:00
if ( router ( $utopia , $dbForPlatform , $getProjectDB , $swooleRequest , $request , $response , $log , $queueForEvents , $queueForStatsUsage , $queueForFunctions , $executor , $geodb , $isResourceBlocked , $previewHostname , $apiKey )) {
2025-02-04 13:16:17 +00:00
$utopia -> getRoute () ? -> label ( 'router' , true );
2024-11-20 12:48:42 +00:00
}
2024-05-23 10:24:12 +00:00
}
2020-12-26 12:19:46 +00:00
});
2019-10-24 17:53:37 +00:00
2023-06-02 11:18:34 +00:00
App :: get ( '/.well-known/acme-challenge/*' )
2020-02-18 22:13:18 +00:00
-> desc ( 'SSL Verification' )
-> label ( 'scope' , 'public' )
-> label ( 'docs' , false )
2020-12-26 12:19:46 +00:00
-> inject ( 'request' )
-> inject ( 'response' )
2022-05-26 14:46:08 +00:00
-> action ( function ( Request $request , Response $response ) {
2022-02-11 08:44:04 +00:00
$uriChunks = \explode ( '/' , $request -> getURI ());
$token = $uriChunks [ \count ( $uriChunks ) - 1 ];
2023-03-01 12:00:36 +00:00
$validator = new Text ( 100 , allowList : [
2022-02-11 08:44:04 +00:00
... Text :: NUMBERS ,
... Text :: ALPHABET_LOWER ,
... Text :: ALPHABET_UPPER ,
'-' ,
'_'
]);
2022-01-31 15:04:30 +00:00
2022-02-11 08:44:04 +00:00
if ( ! $validator -> isValid ( $token ) || \count ( $uriChunks ) !== 4 ) {
2022-08-08 14:44:07 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_ARGUMENT_INVALID , 'Invalid challenge token.' );
2022-01-31 15:04:30 +00:00
}
2020-06-29 21:43:34 +00:00
$base = \realpath ( APP_STORAGE_CERTIFICATES );
2022-05-23 14:54:50 +00:00
$absolute = \realpath ( $base . '/.well-known/acme-challenge/' . $token );
2020-02-23 08:55:57 +00:00
2020-10-27 19:46:15 +00:00
if ( ! $base ) {
2022-08-08 14:44:07 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVER_ERROR , 'Storage error' );
2020-06-29 21:43:34 +00:00
}
2020-02-18 22:13:18 +00:00
2020-10-27 19:46:15 +00:00
if ( ! $absolute ) {
2022-08-08 14:44:07 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND , 'Unknown path' );
2020-06-29 21:43:34 +00:00
}
2020-02-23 08:55:57 +00:00
2020-10-27 19:46:15 +00:00
if ( ! \substr ( $absolute , 0 , \strlen ( $base )) === $base ) {
2022-08-08 14:44:07 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_UNAUTHORIZED_SCOPE , 'Invalid path' );
2020-06-29 21:43:34 +00:00
}
2020-02-23 17:45:51 +00:00
2020-10-27 19:46:15 +00:00
if ( ! \file_exists ( $absolute )) {
2022-08-08 14:44:07 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND , 'Unknown path' );
2020-06-29 21:43:34 +00:00
}
2020-02-23 08:55:57 +00:00
2020-06-29 21:43:34 +00:00
$content = @ \file_get_contents ( $absolute );
2020-02-18 22:13:18 +00:00
2020-10-27 19:46:15 +00:00
if ( ! $content ) {
2022-08-08 14:44:07 +00:00
throw new AppwriteException ( AppwriteException :: GENERAL_SERVER_ERROR , 'Failed to get contents' );
2020-02-18 22:13:18 +00:00
}
2020-06-29 21:43:34 +00:00
$response -> text ( $content );
2020-12-26 12:19:46 +00:00
});
2020-02-18 22:13:18 +00:00
2020-07-29 07:29:34 +00:00
include_once __DIR__ . '/shared/api.php' ;
2022-11-24 07:53:52 +00:00
include_once __DIR__ . '/shared/api/auth.php' ;
2020-06-25 19:53:36 +00:00
2024-10-04 22:23:18 +00:00
App :: get ( '/v1/ping' )
-> groups ([ 'api' , 'general' ])
-> desc ( 'Test the connection between the Appwrite and the SDK.' )
2024-10-07 14:58:34 +00:00
-> label ( 'scope' , 'global' )
2024-10-04 22:23:18 +00:00
-> label ( 'event' , 'projects.[projectId].ping' )
2025-03-13 09:20:45 +00:00
-> label ( 'sdk' , new Method (
namespace : 'ping' ,
2025-04-25 10:26:10 +00:00
group : null ,
2025-03-13 09:20:45 +00:00
name : 'get' ,
hide : true ,
description : <<< EOT
Send a ping to project as part of onboarding .
EOT ,
auth : [],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_ANY ,
)
],
))
2024-10-04 22:23:18 +00:00
-> inject ( 'response' )
2024-10-07 14:58:34 +00:00
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2024-10-04 22:23:18 +00:00
-> inject ( 'queueForEvents' )
2024-12-12 10:30:26 +00:00
-> action ( function ( Response $response , Document $project , Database $dbForPlatform , Event $queueForEvents ) {
2025-02-14 15:40:08 +00:00
if ( $project -> isEmpty () || $project -> getId () === 'console' ) {
2024-10-04 22:23:18 +00:00
throw new AppwriteException ( AppwriteException :: PROJECT_NOT_FOUND );
}
$pingCount = $project -> getAttribute ( 'pingCount' , 0 ) + 1 ;
$pingedAt = DateTime :: now ();
$project
-> setAttribute ( 'pingCount' , $pingCount )
2024-10-07 13:00:19 +00:00
-> setAttribute ( 'pingedAt' , $pingedAt );
2024-10-04 22:23:18 +00:00
2024-12-12 10:30:26 +00:00
Authorization :: skip ( function () use ( $dbForPlatform , $project ) {
$dbForPlatform -> updateDocument ( 'projects' , $project -> getId (), $project );
2024-10-04 22:23:18 +00:00
});
$queueForEvents
2024-10-07 14:58:34 +00:00
-> setParam ( 'projectId' , $project -> getId ())
2024-10-04 22:23:18 +00:00
-> setPayload ( $response -> output ( $project , Response :: MODEL_PROJECT ));
$response -> text ( 'Pong!' );
});
2025-03-08 12:03:23 +00:00
// Preview authorization
App :: get ( '/_appwrite/authorize' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'previewHostname' )
-> action ( function ( Request $request , Response $response , string $previewHostname ) {
$host = $request -> getHostname () ? ? '' ;
if ( ! empty ( $previewHostname )) {
$host = $previewHostname ;
}
$referrer = $request -> getReferer ();
$protocol = \parse_url ( $request -> getOrigin ( $referrer ), PHP_URL_SCHEME );
$jwt = $request -> getParam ( 'jwt' , '' );
$path = $request -> getParam ( 'path' , '' );
$duration = 60 * 60 * 24 ; // 1 day in seconds
$expire = DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), $duration ));
$response
-> addCookie ( Auth :: $cookieNamePreview , $jwt , ( new \DateTime ( $expire )) -> getTimestamp (), '/' , $host , ( 'https' === $protocol ), true , null )
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> redirect ( $protocol . '://' . $host . $path );
});
2023-02-15 14:50:18 +00:00
App :: wildcard ()
2024-07-18 19:31:24 +00:00
-> groups ([ 'api' ])
-> label ( 'scope' , 'global' )
-> action ( function () {
throw new AppwriteException ( AppwriteException :: GENERAL_ROUTE_NOT_FOUND );
});
2023-02-15 14:50:18 +00:00
2020-10-27 19:46:15 +00:00
foreach ( Config :: getParam ( 'services' , []) as $service ) {
2025-02-03 10:38:36 +00:00
if ( ! empty ( $service [ 'controller' ])) {
2025-02-03 09:32:01 +00:00
include_once $service [ 'controller' ];
}
2021-05-12 05:17:34 +00:00
}
2025-01-17 04:31:39 +00:00
// Check for any errors found while we were initialising the SDK Methods.
if ( ! empty ( Method :: getErrors ())) {
throw new \Exception ( 'Errors found during SDK initialization:' . PHP_EOL . implode ( PHP_EOL , Method :: getErrors ()));
2025-01-17 04:39:16 +00:00
}
2025-02-04 16:56:14 +00:00
2025-04-18 16:06:02 +00:00
// Modules
2024-11-18 08:52:16 +00:00
$platform = new Appwrite ();
2024-12-05 05:40:52 +00:00
$platform -> init ( Service :: TYPE_HTTP );