/**
  * Redirect to the installation page of the application.
  * This method does not send the response.
  *
  * @param  Response  $response    HTTP response.
  * @param  Request   $request     HTTP request.
  * @return void
  */
 static function redirectToInstall(Response $response, Request $request)
 {
     list($dirname) = Uri\split($request->getUrl());
     $response->setStatus(307);
     $response->setHeader('Location', $dirname . '/install.php');
     $response->setBody('The application is not installed. ' . 'You are going to be redirected to the installation page.');
 }
Example #2
0
 /**
  * Returns a principals' collection of files.
  *
  * The passed array contains principal information, and is guaranteed to
  * at least contain a uri item. Other properties may or may not be
  * supplied by the authentication backend.
  *
  * @param array $principalInfo
  * @return void
  */
 function getChildForPrincipal(array $principalInfo)
 {
     $owner = $principalInfo['uri'];
     $acl = [['privilege' => '{DAV:}read', 'principal' => $owner, 'protected' => true], ['privilege' => '{DAV:}write', 'principal' => $owner, 'protected' => true]];
     list(, $principalBaseName) = Uri\split($owner);
     $path = $this->storagePath . '/' . $principalBaseName;
     if (!is_dir($path)) {
         mkdir($path, 0777, true);
     }
     return new Collection($path, $acl, $owner);
 }
Example #3
0
 /**
  * Returns a principals' collection of files.
  *
  * The passed array contains principal information, and is guaranteed to
  * at least contain a uri item. Other properties may or may not be
  * supplied by the authentication backend.
  *
  * @param array $principalInfo
  * @return void
  */
 function getChildForPrincipal(array $principalInfo)
 {
     $owner = $principalInfo['uri'];
     $acl = [['privilege' => '{DAV:}read', 'principal' => $owner, 'protected' => true], ['privilege' => '{DAV:}write', 'principal' => $owner, 'protected' => true]];
     list(, $principalBaseName) = SabreUri\split($owner);
     $path = $this->storagePath . DS . $principalBaseName;
     if (!is_dir($path)) {
         HoaFile\Directory::create($path, HoaFile\Directory::MODE_CREATE_RECURSIVE);
     }
     $public = $path . DS . 'public';
     if (!is_dir($public)) {
         HoaFile\Directory::create($public, HoaFile\Directory::MODE_CREATE_RECURSIVE);
     }
     $out = new Directory($path, $acl, $owner);
     $out->setRelativePath($this->storagePath);
     return $out;
 }
Example #4
0
 /**
  * @param string $prefixPath
  *
  * @return array|null
  */
 public function getPrincipalsByPrefix($prefixPath)
 {
     $dbPrincipals = $this->manager->findAll('public', 'principal');
     if ($dbPrincipals->count() == 0) {
         return null;
     }
     $principals = [];
     foreach ($dbPrincipals as $dbPrincipal) {
         // Checking if the principal is in the prefix
         list($rowPrefix, $basename) = Uri\split($dbPrincipal->uri);
         if ($rowPrefix !== $prefixPath) {
             continue;
         }
         $principal = ['uri' => $dbPrincipal->uri];
         foreach ($this->fieldMap as $key => $value) {
             $principal[$key] = $dbPrincipal->{$value}['dbField'];
         }
         $principals[] = $principal;
     }
     return $principals;
 }
 /**
  * Check if the current path is inside the `public/` directory of the
  * current principal.
  *
  * @return bool
  */
 function isPublic()
 {
     list(, $principalBaseName) = SabreUri\split($this->owner);
     $publicPath = $this->getRelativePath() . DS . $principalBaseName . DS . 'public';
     return $publicPath === substr($this->path, 0, min(strlen($publicPath), strlen($this->path)));
 }
Example #6
0
 /**
  * Main method.
  *
  * @return int
  */
 function main()
 {
     $operation = 0;
     $location = null;
     $updateServer = Updater::DEFAULT_UPDATE_SERVER;
     while (false !== ($c = $this->getOption($v))) {
         switch ($c) {
             case '__ambiguous':
                 $this->resolveOptionAmbiguity($v);
                 break;
             case 'f':
                 $operation = static::OPERATION_FETCH | Updater::FORMAT_PHAR;
                 break;
             case 'z':
                 $operation = static::OPERATION_FETCH | Updater::FORMAT_ZIP;
                 break;
             case 'a':
                 $operation = static::OPERATION_APPLY;
                 $location = $v;
                 break;
             case 's':
                 $updateServer = $v;
                 break;
             case 'h':
             case '?':
             default:
                 return $this->usage();
                 break;
         }
     }
     $updateServer = rtrim($updateServer, '/') . '/';
     if (0 !== (static::OPERATION_FETCH & $operation)) {
         $updatesDotJson = Updater::getUpdateUrl($updateServer);
         $versions = @file_get_contents($updatesDotJson);
         if (empty($versions)) {
             throw new Exception\Console('Oh no! We are not able to check if a new version exists… ' . 'Contact us at http://sabre.io/ ' . '(tried URL %s).', 0, $updatesDotJson);
         }
         $versions = json_decode($versions, true);
         /**
          * Expected format:
          *     {
          *         "1.0.1": {
          *             "phar": "https://…",
          *             "zip" : "https://…"
          *         },
          *         "1.0.0": {
          *             "phar": "https://…",
          *             "zip" : "https://…"
          *         },
          *         …
          *     }
          */
         $versionsToFetch = Updater::filterVersions($versions, Version::VERSION, $operation);
         $windowWidth = Window::getSize()['x'];
         $progress = function ($percent) use($windowWidth) {
             Cursor::clear('↔');
             $message = 'Downloading… ';
             $barWidth = $windowWidth - mb_strlen($message);
             if ($percent <= 0) {
                 $color = '#c74844';
             } elseif ($percent <= 25) {
                 $color = '#cb9a3d';
             } elseif ($percent <= 50) {
                 $color = '#dcb11e';
             } elseif ($percent <= 75) {
                 $color = '#aed633';
             } else {
                 $color = '#54b455';
             }
             echo $message;
             Cursor::colorize('foreground(' . $color . ') background(' . $color . ')');
             echo str_repeat('|', $percent * $barWidth / 100);
             Cursor::colorize('normal');
         };
         foreach ($versionsToFetch as $versionNumber => $versionUrl) {
             list(, $versionUrlBasename) = Uri\split($versionUrl);
             $fileIn = new File\Read($versionUrl, File::MODE_READ, null, true);
             $fileOut = new File\Write(SABRE_KATANA_PREFIX . '/data/share/update/' . $versionUrlBasename);
             echo "\n", 'Fetch version ', $versionNumber, ' from ', $versionUrl, "\n", 'Waiting…', "\n";
             $fileIn->on('connect', function () {
                 Cursor::clear('↔');
                 echo 'Downloading… ';
             });
             $fileIn->on('progress', function (Core\Event\Bucket $bucket) use($progress) {
                 static $previousPercent = 0;
                 $data = $bucket->getData();
                 $current = $data['transferred'];
                 $max = $data['max'];
                 $percent = $current * 100 / $max;
                 $delta = $percent - $previousPercent;
                 if (1 <= $delta) {
                     $previousPercent = $percent;
                     $progress($percent);
                 }
             });
             $fileIn->open();
             $fileOut->writeAll($fileIn->readAll());
             echo "\n", 'Fetched at ', $fileOut->getStreamName(), '.', "\n";
         }
         return 0;
     } elseif (static::OPERATION_APPLY === $operation) {
         if (false === file_exists($location)) {
             throw new Exception\Console('Update %s is not found.', 1, $location);
         }
         $processus = new Console\Processus(Core::getPHPBinary(), [$location, '--extract' => SABRE_KATANA_PREFIX, '--overwrite']);
         $processus->on('input', function () {
             return false;
         });
         $processus->on('output', function (Core\Event\Bucket $bucket) {
             echo $bucket->getData()['line'], "\n";
         });
         $processus->run();
         if (true === $processus->isSuccessful()) {
             echo 'sabre/katana updated!', "\n";
         } else {
             echo 'Something wrong happened!', "\n";
         }
         return $processus->getExitCode();
     } else {
         return $this->usage();
     }
 }
Example #7
0
 /**
  * Returns the name of this object
  *
  * @return string
  */
 function getName()
 {
     list(, $name) = Uri\split($this->principalUri);
     return $name;
 }
Example #8
0
 /**
  * Checks if the submitted iCalendar data is in fact, valid.
  *
  * An exception is thrown if it's not.
  *
  * @param resource|string $data
  * @param string $path
  * @param bool $modified Should be set to true, if this event handler
  *                       changed &$data.
  * @param RequestInterface $request The http request.
  * @param ResponseInterface $response The http response.
  * @param bool $isNew Is the item a new one, or an update.
  * @return void
  */
 protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew)
 {
     // If it's a stream, we convert it to a string first.
     if (is_resource($data)) {
         $data = stream_get_contents($data);
     }
     $before = md5($data);
     // Converting the data to unicode, if needed.
     $data = DAV\StringUtil::ensureUTF8($data);
     if ($before !== md5($data)) {
         $modified = true;
     }
     try {
         // If the data starts with a [, we can reasonably assume we're dealing
         // with a jCal object.
         if (substr($data, 0, 1) === '[') {
             $vobj = VObject\Reader::readJson($data);
             // Converting $data back to iCalendar, as that's what we
             // technically support everywhere.
             $data = $vobj->serialize();
             $modified = true;
         } else {
             $vobj = VObject\Reader::read($data);
         }
     } catch (VObject\ParseException $e) {
         throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
     }
     if ($vobj->name !== 'VCALENDAR') {
         throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
     }
     $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
     // Get the Supported Components for the target calendar
     list($parentPath) = Uri\split($path);
     $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
     if (isset($calendarProperties[$sCCS])) {
         $supportedComponents = $calendarProperties[$sCCS]->getValue();
     } else {
         $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
     }
     $foundType = null;
     $foundUID = null;
     foreach ($vobj->getComponents() as $component) {
         switch ($component->name) {
             case 'VTIMEZONE':
                 continue 2;
             case 'VEVENT':
             case 'VTODO':
             case 'VJOURNAL':
                 if (is_null($foundType)) {
                     $foundType = $component->name;
                     if (!in_array($foundType, $supportedComponents)) {
                         throw new Exception\InvalidComponentType('This calendar only supports ' . implode(', ', $supportedComponents) . '. We found a ' . $foundType);
                     }
                     if (!isset($component->UID)) {
                         throw new DAV\Exception\BadRequest('Every ' . $component->name . ' component must have an UID');
                     }
                     $foundUID = (string) $component->UID;
                 } else {
                     if ($foundType !== $component->name) {
                         throw new DAV\Exception\BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
                     }
                     if ($foundUID !== (string) $component->UID) {
                         throw new DAV\Exception\BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
                     }
                 }
                 break;
             default:
                 throw new DAV\Exception\BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
         }
     }
     if (!$foundType) {
         throw new DAV\Exception\BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
     }
     // We use an extra variable to allow event handles to tell us wether
     // the object was modified or not.
     //
     // This helps us determine if we need to re-serialize the object.
     $subModified = false;
     $this->server->emit('calendarObjectChange', [$request, $response, $vobj, $parentPath, &$subModified, $isNew]);
     if ($subModified) {
         // An event handler told us that it modified the object.
         $data = $vobj->serialize();
         // Using md5 to figure out if there was an *actual* change.
         if (!$modified && $before !== md5($data)) {
             $modified = true;
         }
     }
 }
Example #9
0
 /**
  * Main method.
  *
  * @return int
  */
 function main()
 {
     $verbose = !(Console::isDirect(STDOUT) || !OS_WIN);
     while (false !== ($c = $this->getOption($v))) {
         switch ($c) {
             case '__ambiguous':
                 $this->resolveOptionAmbiguity($v);
                 break;
             case 'v':
                 $verbose = $v;
                 break;
             case 'h':
             case '?':
             default:
                 return $this->usage();
         }
     }
     if (true === Installer::isInstalled()) {
         echo 'The application is already installed.', "\n";
         return 1;
     }
     $oldTitle = Window::getTitle();
     Window::setTitle('Installation of sabre/katana');
     $form = ['baseUrl' => '/', 'email' => null, 'password' => null, 'database' => ['driver' => 'sqlite', 'host' => '', 'port' => '', 'name' => '', 'username' => '', 'password' => '']];
     $readline = new Console\Readline();
     if (true === $verbose) {
         $windowWidth = Window::getSize()['x'];
         $labelMaxWidth = 35;
         $inputMaxWidth = $windowWidth - $labelMaxWidth;
         $numberOfSteps = 5;
         $input = function ($default = '') use($inputMaxWidth) {
             return Text::colorize($default . str_repeat(' ', $inputMaxWidth - mb_strlen($default)), 'foreground(black) background(#cccccc)');
         };
         $resetInput = function ($default = '') use($input, $labelMaxWidth) {
             Cursor::move('→', $labelMaxWidth);
             echo $input($default);
             Cursor::move('LEFT');
             Cursor::move('→', $labelMaxWidth);
             Cursor::colorize('foreground(black) background(#cccccc)');
         };
         echo Text::colorize('Installation of sabre/' . "\n" . Welcome::LOGO, 'foreground(yellow)'), "\n\n", static::getBaseURLInfo(), "\n\n", 'Choose the base URL:               ', $input('/'), "\n", 'Your administrator login:          '******'Choose the administrator password: '******'Choose the administrator email:    ', $input(), "\n", 'Choose the database driver:        ', '🔘 SQLite ⚪️ MySQL', "\n";
         Window::scroll('↑', 10);
         Cursor::move('↑', 10);
         Cursor::move('↑', $numberOfSteps);
         Cursor::move('→', $labelMaxWidth);
         // Disable arrow up and down.
         $no_echo = function ($readline) {
             return $readline::STATE_NO_ECHO;
         };
         $readline->addMapping("", $no_echo);
         $readline->addMapping("", $no_echo);
         $step = function ($index, $label, callable $validator, $errorMessage, $default = '') use($numberOfSteps, &$readline, $resetInput, $labelMaxWidth) {
             Cursor::colorize('foreground(black) background(#cccccc)');
             do {
                 $out = $readline->readLine();
                 if (empty($out)) {
                     $out = $default;
                 }
                 $valid = $validator($out);
                 if (true !== $valid) {
                     Cursor::move('↑');
                     $resetInput($default);
                     Cursor::save();
                     Cursor::move('LEFT');
                     Cursor::move('↓', $numberOfSteps - $index + 1);
                     list($title, $message) = explode("\n", $errorMessage);
                     Cursor::colorize('foreground(white) background(red)');
                     echo $title, "\n";
                     Cursor::colorize('foreground(red) background(normal)');
                     echo $message;
                     Cursor::restore();
                 } else {
                     Cursor::save();
                     Cursor::move('LEFT');
                     Cursor::move('↓', $numberOfSteps - $index - 1);
                     Cursor::colorize('normal');
                     Cursor::clear('↓');
                     Cursor::restore();
                 }
             } while (true !== $valid);
             if ($numberOfSteps !== $index + 1) {
                 Cursor::move('→', $labelMaxWidth);
             }
             Cursor::colorize('normal');
             return $out;
         };
         $progress = function ($percent, $message) use($windowWidth) {
             static $margin = 4;
             $barWidth = $windowWidth - $margin * 2;
             Cursor::move('LEFT');
             Cursor::move('↑', 1);
             Cursor::clear('↓');
             if ($percent <= 0) {
                 $color = '#c74844';
             } elseif ($percent <= 25) {
                 $color = '#cb9a3d';
             } elseif ($percent <= 50) {
                 $color = '#dcb11e';
             } elseif ($percent <= 75) {
                 $color = '#aed633';
             } else {
                 $color = '#54b455';
             }
             echo str_repeat(' ', $margin);
             Cursor::colorize('foreground(' . $color . ') background(' . $color . ')');
             echo str_repeat('|', $percent * $barWidth / 100);
             Cursor::move('LEFT ↓');
             Cursor::colorize('background(normal)');
             echo str_repeat(' ', $margin) . $message;
             Cursor::colorize('normal');
             sleep(1);
         };
     } else {
         echo 'Installation of sabre/' . "\n" . Welcome::LOGO, "\n\n", static::getBaseURLInfo(), "\n\n";
         $step = function ($index, $label, callable $validator, $errorMessage, $default = '') use(&$readline) {
             do {
                 echo $label;
                 if (!empty($default)) {
                     echo ' [default: ', $default, ']';
                 }
                 $out = $readline->readLine(': ');
                 if (empty($out)) {
                     $out = $default;
                 }
                 $valid = $validator($out);
                 if (true !== $valid) {
                     echo $errorMessage, "\n";
                 }
             } while (true !== $valid);
             return $out;
         };
         $progress = function ($percent, $message) {
             echo $message, "\n";
         };
     }
     $form['baseUrl'] = $step(0, 'Choose the base URL', function ($baseUrl) use($verbose) {
         $valid = Installer::checkBaseUrl($baseUrl);
         if (true === $valid && true === $verbose) {
             Cursor::move('↓');
         }
         return $valid;
     }, 'Base URL must start and end by a slash' . "\n" . 'Check the Section “The base URL” on http://sabre.io/dav/gettingstarted/.', '/');
     if (false === $verbose) {
         echo 'Your administrator login: '******'password'] = $step(1, 'Choose the administrator password', function ($administratorPassword) {
         return Installer::checkPassword($administratorPassword . $administratorPassword);
     }, 'Password must not be empty' . "\n" . 'An empty password is not a password anymore!');
     $readline = $oldReadline;
     $form['email'] = $step(2, 'Choose the administrator email', function ($administratorEmail) {
         return Installer::checkEmail($administratorEmail . $administratorEmail);
     }, 'Email is invalid' . "\n" . 'The given email seems invalid.');
     $databaseDriver =& $form['database']['driver'];
     if (true === $verbose) {
         $radioReadline = new Console\Readline\Password();
         $radioReadline->addMapping('\\e[D', function () use($labelMaxWidth, &$databaseDriver) {
             $databaseDriver = 'sqlite';
             Cursor::save();
             Cursor::move('LEFT');
             Cursor::move('→', $labelMaxWidth);
             Cursor::clear('→');
             echo '🔘 SQLite ⚪️ MySQL';
             Cursor::restore();
         });
         $radioReadline->addMapping('\\e[C', function () use($labelMaxWidth, &$databaseDriver) {
             $databaseDriver = 'mysql';
             Cursor::save();
             Cursor::move('LEFT');
             Cursor::move('→', $labelMaxWidth);
             Cursor::clear('→');
             echo '⚪️ SQLite 🔘 MySQL';
             Cursor::restore();
         });
         Cursor::hide();
         $radioReadline->readLine();
         Cursor::show();
         unset($databaseDriver);
         if ('mysql' === $form['database']['driver']) {
             echo 'Choose MySQL host:                 ', $input(), "\n", 'Choose MySQL port:                 ', $input('3306'), "\n", 'Choose MySQL username:             '******'Choose MySQL password:             '******'Choose MySQL database name:        ', $input(), "\n";
             Window::scroll('↑', 10);
             Cursor::move('↑', 10);
             $numberOfSteps = 5;
             Cursor::move('↑', $numberOfSteps);
             Cursor::move('→', $labelMaxWidth);
             Cursor::colorize('foreground(black) background(#cccccc)');
         }
     } else {
         $form['database']['driver'] = $step(3, 'Choose the database driver (sqlite or mysql)', function ($databaseDriver) {
             return in_array($databaseDriver, ['sqlite', 'mysql']);
         }, 'Database driver is invalid' . "\n" . 'Database driver must be `sqlite` or `mysql`', 'sqlite');
     }
     if ('mysql' === $form['database']['driver']) {
         $form['database']['host'] = $step(0, 'Choose MySQL host', function () {
             return true;
         }, '');
         $form['database']['port'] = $step(1, 'Choose MySQL port', function ($port) {
             return false !== filter_var($port, FILTER_VALIDATE_INT);
         }, 'Port is invalid' . "\n" . 'Port must be an integer.', '3306');
         $form['database']['username'] = $step(2, 'Choose MySQL username', function () {
             return true;
         }, '');
         $oldReadline = $readline;
         $readline = new Console\Readline\Password();
         $form['database']['password'] = $step(3, 'Choose MySQL password', function () {
             return true;
         }, '');
         $readline = $oldReadline;
         $form['database']['name'] = $step(3, 'Choose MySQL database name', function () {
             return true;
         }, '');
     }
     $readline->readLine("\n" . 'Ready to install? (Enter to continue, Ctrl-C to abort)');
     echo "\n\n";
     try {
         $progress(5, 'Create configuration file…');
         $configuration = Installer::createConfigurationFile(Server::CONFIGURATION_FILE, ['baseUrl' => $form['baseUrl'], 'database' => $form['database']]);
         $progress(25, 'Configuration file created 👍!');
         $progress(30, 'Create the database…');
         $database = Installer::createDatabase($configuration);
         $progress(50, 'Database created 👍!');
         $progress(55, 'Create administrator profile…');
         Installer::createAdministratorProfile($configuration, $database, $form['email'], $form['password']);
         $progress(75, 'Administrator profile created 👍!');
         $progress(100, 'sabre/katana is ready!');
     } catch (\Exception $e) {
         $progress(-1, 'An error occured: ' . $e->getMessage());
         if (null !== ($previous = $e->getPrevious())) {
             echo 'Underlying error: ' . $previous->getMessage();
         }
         echo "\n", 'You are probably likely to run: ' . '`make uninstall` before trying again.', "\n";
         return 2;
     }
     list($dirname) = Uri\split($form['baseUrl']);
     echo "\n\n", 'The administration interface will be found at this path: ', '<your website>', $dirname, '/admin.php.', "\n";
     Window::setTitle($oldTitle);
 }
Example #10
0
 /**
  * Triggered before a node is deleted
  *
  * This allows us to check permissions for any operation that will delete
  * an existing node.
  *
  * @param string $uri
  * @return void
  */
 function beforeUnbind($uri)
 {
     list($parentUri) = Uri\split($uri);
     $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS);
 }
Example #11
0
 /**
  * Triggered by a `DELETE`, `COPY` or `MOVE`. The goal is to remove the
  * home directory of the principal.
  *
  * @param  string  $path    Path.
  * @return bool
  */
 function afterUnbind($path)
 {
     list($collection, $principalName) = SabreUri\split($path);
     if ('principals' !== $collection) {
         return false;
     }
     $out = true;
     $path = $this->storagePath . DS . $principalName;
     if (is_dir($path)) {
         $directory = new HoaFile\Directory($this->storagePath . DS . $principalName);
         $out = $directory->delete();
         $directory->close();
     }
     return $out;
 }
Example #12
0
 /**
  * Checks if the submitted iCalendar data is in fact, valid.
  *
  * An exception is thrown if it's not.
  *
  * @param resource|string $data
  * @param string $path
  * @param bool $modified Should be set to true, if this event handler
  *                       changed &$data.
  * @param RequestInterface $request The http request.
  * @param ResponseInterface $response The http response.
  * @param bool $isNew Is the item a new one, or an update.
  * @return void
  */
 protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew)
 {
     // If it's a stream, we convert it to a string first.
     if (is_resource($data)) {
         $data = stream_get_contents($data);
     }
     $before = $data;
     try {
         // If the data starts with a [, we can reasonably assume we're dealing
         // with a jCal object.
         if (substr($data, 0, 1) === '[') {
             $vobj = VObject\Reader::readJson($data);
             // Converting $data back to iCalendar, as that's what we
             // technically support everywhere.
             $data = $vobj->serialize();
             $modified = true;
         } else {
             $vobj = VObject\Reader::read($data);
         }
     } catch (VObject\ParseException $e) {
         throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());
     }
     if ($vobj->name !== 'VCALENDAR') {
         throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
     }
     $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
     // Get the Supported Components for the target calendar
     list($parentPath) = Uri\split($path);
     $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);
     if (isset($calendarProperties[$sCCS])) {
         $supportedComponents = $calendarProperties[$sCCS]->getValue();
     } else {
         $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
     }
     $foundType = null;
     foreach ($vobj->getComponents() as $component) {
         switch ($component->name) {
             case 'VTIMEZONE':
                 continue 2;
             case 'VEVENT':
             case 'VTODO':
             case 'VJOURNAL':
                 $foundType = $component->name;
                 break;
         }
     }
     if (!$foundType || !in_array($foundType, $supportedComponents)) {
         throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents));
     }
     $options = VObject\Node::PROFILE_CALDAV;
     $prefer = $this->server->getHTTPPrefer();
     if ($prefer['handling'] !== 'strict') {
         $options |= VObject\Node::REPAIR;
     }
     $messages = $vobj->validate($options);
     $highestLevel = 0;
     $warningMessage = null;
     // $messages contains a list of problems with the vcard, along with
     // their severity.
     foreach ($messages as $message) {
         if ($message['level'] > $highestLevel) {
             // Recording the highest reported error level.
             $highestLevel = $message['level'];
             $warningMessage = $message['message'];
         }
         switch ($message['level']) {
             case 1:
                 // Level 1 means that there was a problem, but it was repaired.
                 $modified = true;
                 break;
             case 2:
                 // Level 2 means a warning, but not critical
                 break;
             case 3:
                 // Level 3 means a critical error
                 throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: ' . $message['message']);
         }
     }
     if ($warningMessage) {
         $response->setHeader('X-Sabre-Ew-Gross', 'iCalendar validation warning: ' . $warningMessage);
     }
     // We use an extra variable to allow event handles to tell us wether
     // the object was modified or not.
     //
     // This helps us determine if we need to re-serialize the object.
     $subModified = false;
     $this->server->emit('calendarObjectChange', [$request, $response, $vobj, $parentPath, &$subModified, $isNew]);
     if ($modified || $subModified) {
         // An event handler told us that it modified the object.
         $data = $vobj->serialize();
         // Using md5 to figure out if there was an *actual* change.
         if (!$modified && strcmp($data, $before) !== 0) {
             $modified = true;
         }
     }
     // Destroy circular references so PHP will garbage collect the object.
     $vobj->destroy();
 }