/** * serialize * * @param DAV\Server $server * @param \DOMElement $dom * @return void */ public function serialize(DAV\Server $server, \DOMElement $dom) { $document = $dom->ownerDocument; $properties = $this->responseProperties; $xresponse = $document->createElement('d:response'); $dom->appendChild($xresponse); $uri = DAV\URLUtil::encodePath($this->href); // Adding the baseurl to the beginning of the url $uri = $server->getBaseUri() . $uri; $xresponse->appendChild($document->createElement('d:href', $uri)); // The properties variable is an array containing properties, grouped by // HTTP status foreach ($properties as $httpStatus => $propertyGroup) { // The 'href' is also in this array, and it's special cased. // We will ignore it if ($httpStatus == 'href') { continue; } // If there are no properties in this group, we can also just carry on if (!count($propertyGroup)) { continue; } $xpropstat = $document->createElement('d:propstat'); $xresponse->appendChild($xpropstat); $xprop = $document->createElement('d:prop'); $xpropstat->appendChild($xprop); $nsList = $server->xmlNamespaces; foreach ($propertyGroup as $propertyName => $propertyValue) { $propName = null; preg_match('/^{([^}]*)}(.*)$/', $propertyName, $propName); // special case for empty namespaces if ($propName[1] == '') { $currentProperty = $document->createElement($propName[2]); $xprop->appendChild($currentProperty); $currentProperty->setAttribute('xmlns', ''); } else { if (!isset($nsList[$propName[1]])) { $nsList[$propName[1]] = 'x' . count($nsList); } // If the namespace was defined in the top-level xml namespaces, it means // there was already a namespace declaration, and we don't have to worry about it. if (isset($server->xmlNamespaces[$propName[1]])) { $currentProperty = $document->createElement($nsList[$propName[1]] . ':' . $propName[2]); } else { $currentProperty = $document->createElementNS($propName[1], $nsList[$propName[1]] . ':' . $propName[2]); } $xprop->appendChild($currentProperty); } if (is_scalar($propertyValue)) { $text = $document->createTextNode($propertyValue); $currentProperty->appendChild($text); } elseif ($propertyValue instanceof DAV\PropertyInterface) { $propertyValue->serialize($server, $currentProperty); } elseif (!is_null($propertyValue)) { throw new DAV\Exception('Unknown property value type: ' . gettype($propertyValue) . ' for property: ' . $propertyName); } } $xpropstat->appendChild($document->createElement('d:status', $server->httpResponse->getStatusMessage($httpStatus))); } }
/** * Renames the node * * @param string $name The new name * @return void */ public function setName($name) { list($parentPath, ) = DAV\URLUtil::splitPath($this->path); list(, $newName) = DAV\URLUtil::splitPath($name); $newPath = $parentPath . '/' . $newName; rename($this->path, $newPath); $this->path = $newPath; }
/** * Handler for teh afterGetProperties event * * @param string $path * @param array $properties * @return void */ public function afterGetProperties($path, &$properties) { if (array_key_exists('{DAV:}getcontenttype', $properties[404])) { list(, $fileName) = DAV\URLUtil::splitPath($path); $contentType = $this->getContentType($fileName); if ($contentType) { $properties[200]['{DAV:}getcontenttype'] = $contentType; unset($properties[404]['{DAV:}getcontenttype']); } } }
/** * Serializes this property. * * It will additionally prepend the href property with the server's base uri. * * @param DAV\Server $server * @param \DOMElement $dom * @return void */ public function serialize(DAV\Server $server, \DOMElement $dom) { $prefix = $server->xmlNamespaces['DAV:']; $elem = $dom->ownerDocument->createElement($prefix . ':href'); if ($this->autoPrefix) { $value = $server->getBaseUri() . DAV\URLUtil::encodePath($this->href); } else { $value = $this->href; } $elem->appendChild($dom->ownerDocument->createTextNode($value)); $dom->appendChild($elem); }
/** * 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 * @return void */ protected function validateICalendar(&$data, $path) { // If it's a stream, we convert it to a string first. if (is_resource($data)) { $data = stream_get_contents($data); } // Converting the data to unicode, if needed. $data = DAV\StringUtil::ensureUTF8($data); try { $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.'); } // Get the Supported Components for the target calendar list($parentPath, $object) = DAV\URLUtil::splitPath($path); $calendarProperties = $this->server->getProperties($parentPath, array('{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set')); $supportedComponents = $calendarProperties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue(); $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'); } }
/** * Returns the name of this object * * @return string */ public function getName() { list(, $name) = DAV\URLUtil::splitPath($this->principalInfo['uri']); return $name; }
/** * This method is used to search for principals matching a set of * properties. * * This search is specifically used by RFC3744's principal-property-search * REPORT. You should at least allow searching on * http://sabredav.org/ns}email-address. * * The actual search should be a unicode-non-case-sensitive search. The * keys in searchProperties are the WebDAV property names, while the values * are the property values to search on. * * If multiple properties are being searched on, the search should be * AND'ed. * * This method should simply return an array with full principal uri's. * * If somebody attempted to search on a property the backend does not * support, you should simply return 0 results. * * You can also just return 0 results if you choose to not support * searching at all, but keep in mind that this may stop certain features * from working. * * @param string $prefixPath * @param array $searchProperties * @return array */ public function searchPrincipals($prefixPath, array $searchProperties) { $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE 1=1 '; $values = array(); foreach ($searchProperties as $property => $value) { switch ($property) { case '{DAV:}displayname': $query .= ' AND displayname LIKE ?'; $values[] = '%' . $value . '%'; break; case '{http://sabredav.org/ns}email-address': $query .= ' AND email LIKE ?'; $values[] = '%' . $value . '%'; break; default: // Unsupported property return array(); } } $stmt = $this->pdo->prepare($query); $stmt->execute($values); $principals = array(); while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { // Checking if the principal is in the prefix list($rowPrefix) = DAV\URLUtil::splitPath($row['uri']); if ($rowPrefix !== $prefixPath) { continue; } $principals[] = $row['uri']; } return $principals; }
/** * 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 */ public function beforeUnbind($uri) { list($parentUri, $nodeName) = DAV\URLUtil::splitPath($uri); $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS); }
/** * Serializes the property into a DOMElement. * * @param DAV\Server $server * @param \DOMElement $node * @return void */ public function serialize(DAV\Server $server, \DOMElement $node) { $prefix = $server->xmlNamespaces['DAV:']; switch ($this->type) { case self::UNAUTHENTICATED: $node->appendChild($node->ownerDocument->createElement($prefix . ':unauthenticated')); break; case self::AUTHENTICATED: $node->appendChild($node->ownerDocument->createElement($prefix . ':authenticated')); break; case self::HREF: $href = $node->ownerDocument->createElement($prefix . ':href'); $href->nodeValue = $server->getBaseUri() . DAV\URLUtil::encodePath($this->href); $node->appendChild($href); break; } }
/** * Returns this principals name. * * @return string */ public function getName() { $uri = $this->principalProperties['uri']; list(, $name) = DAV\URLUtil::splitPath($uri); return $name; }
/** * Generates the html directory index for a given url * * @param string $path * @return string */ public function generateDirectoryIndex($path) { $version = ''; if (DAV\Server::$exposeVersion) { $version = DAV\Version::VERSION . "-" . DAV\Version::STABILITY; } $html = "<html>\n<head>\n <title>Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . "</title>\n <style type=\"text/css\">\n body { Font-family: arial}\n h1 { font-size: 150% }\n </style>\n "; if ($this->enableAssets) { $html .= '<link rel="shortcut icon" href="' . $this->getAssetUrl('favicon.ico') . '" type="image/vnd.microsoft.icon" />'; } $html .= "</head>\n<body>\n <h1>Index for " . $this->escapeHTML($path) . "/</h1>\n <table>\n <tr><th width=\"24\"></th><th>Name</th><th>Type</th><th>Size</th><th>Last modified</th></tr>\n <tr><td colspan=\"5\"><hr /></td></tr>"; $files = $this->server->getPropertiesForPath($path, array('{DAV:}displayname', '{DAV:}resourcetype', '{DAV:}getcontenttype', '{DAV:}getcontentlength', '{DAV:}getlastmodified'), 1); $parent = $this->server->tree->getNodeForPath($path); if ($path) { list($parentUri) = DAV\URLUtil::splitPath($path); $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); $icon = $this->enableAssets ? '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>' : ''; $html .= "<tr>\n <td>{$icon}</td>\n <td><a href=\"{$fullPath}\">..</a></td>\n <td>[parent]</td>\n <td></td>\n <td></td>\n </tr>"; } foreach ($files as $file) { // This is the current directory, we can skip it if (rtrim($file['href'], '/') == $path) { continue; } list(, $name) = DAV\URLUtil::splitPath($file['href']); $type = null; if (isset($file[200]['{DAV:}resourcetype'])) { $type = $file[200]['{DAV:}resourcetype']->getValue(); // resourcetype can have multiple values if (!is_array($type)) { $type = array($type); } foreach ($type as $k => $v) { // Some name mapping is preferred switch ($v) { case '{DAV:}collection': $type[$k] = 'Collection'; break; case '{DAV:}principal': $type[$k] = 'Principal'; break; case '{urn:ietf:params:xml:ns:carddav}addressbook': $type[$k] = 'Addressbook'; break; case '{urn:ietf:params:xml:ns:caldav}calendar': $type[$k] = 'Calendar'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-inbox': $type[$k] = 'Schedule Inbox'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-outbox': $type[$k] = 'Schedule Outbox'; break; case '{http://calendarserver.org/ns/}calendar-proxy-read': $type[$k] = 'Proxy-Read'; break; case '{http://calendarserver.org/ns/}calendar-proxy-write': $type[$k] = 'Proxy-Write'; break; } } $type = implode(', ', $type); } // If no resourcetype was found, we attempt to use // the contenttype property if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { $type = $file[200]['{DAV:}getcontenttype']; } if (!$type) { $type = 'Unknown'; } $size = isset($file[200]['{DAV:}getcontentlength']) ? (int) $file[200]['{DAV:}getcontentlength'] : ''; $lastmodified = isset($file[200]['{DAV:}getlastmodified']) ? $file[200]['{DAV:}getlastmodified']->getTime()->format(\DateTime::ATOM) : ''; $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/')); $displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name; $displayName = $this->escapeHTML($displayName); $type = $this->escapeHTML($type); $icon = ''; if ($this->enableAssets) { $node = $this->server->tree->getNodeForPath(($path ? $path . '/' : '') . $name); foreach (array_reverse($this->iconMap) as $class => $iconName) { if ($node instanceof $class) { $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>'; break; } } } $html .= "<tr>\n <td>{$icon}</td>\n <td><a href=\"{$fullPath}\">{$displayName}</a></td>\n <td>{$type}</td>\n <td>{$size}</td>\n <td>{$lastmodified}</td>\n </tr>"; } $html .= "<tr><td colspan=\"5\"><hr /></td></tr>"; $output = ''; if ($this->enablePost) { $this->server->broadcastEvent('onHTMLActionsPanel', array($parent, &$output)); } $html .= $output; $html .= "</table>\n <address>Generated by SabreDAV " . $version . " (c)2007-2013 <a href=\"http://code.google.com/p/sabredav/\">http://code.google.com/p/sabredav/</a></address>\n </body>\n </html>"; return $html; }
/** * Use this method to create a new collection * * The {DAV:}resourcetype is specified using the resourceType array. * At the very least it must contain {DAV:}collection. * * The properties array can contain a list of additional properties. * * @param string $uri The new uri * @param array $resourceType The resourceType(s) * @param array $properties A list of properties * @return array|null */ public function createCollection($uri, array $resourceType, array $properties) { list($parentUri, $newName) = URLUtil::splitPath($uri); // Making sure {DAV:}collection was specified as resourceType if (!in_array('{DAV:}collection', $resourceType)) { throw new Exception\InvalidResourceType('The resourceType for this collection must at least include {DAV:}collection'); } // Making sure the parent exists try { $parent = $this->tree->getNodeForPath($parentUri); } catch (Exception\NotFound $e) { throw new Exception\Conflict('Parent node does not exist'); } // Making sure the parent is a collection if (!$parent instanceof ICollection) { throw new Exception\Conflict('Parent node is not a collection'); } // Making sure the child does not already exist try { $parent->getChild($newName); // If we got here.. it means there's already a node on that url, and we need to throw a 405 throw new Exception\MethodNotAllowed('The resource you tried to create already exists'); } catch (Exception\NotFound $e) { // This is correct } if (!$this->broadcastEvent('beforeBind', array($uri))) { return; } // There are 2 modes of operation. The standard collection // creates the directory, and then updates properties // the extended collection can create it directly. if ($parent instanceof IExtendedCollection) { $parent->createExtendedCollection($newName, $resourceType, $properties); } else { // No special resourcetypes are supported if (count($resourceType) > 1) { throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.'); } $parent->createDirectory($newName); $rollBack = false; $exception = null; $errorResult = null; if (count($properties) > 0) { try { $errorResult = $this->updateProperties($uri, $properties); if (!isset($errorResult[200])) { $rollBack = true; } } catch (Exception $e) { $rollBack = true; $exception = $e; } } if ($rollBack) { if (!$this->broadcastEvent('beforeUnbind', array($uri))) { return; } $this->tree->delete($uri); // Re-throwing exception if ($exception) { throw $exception; } return $errorResult; } } $this->tree->markDirty($parentUri); $this->broadcastEvent('afterBind', array($uri)); }
/** * This method is used to search for principals matching a set of * properties. * * This search is specifically used by RFC3744's principal-property-search * REPORT. You should at least allow searching on * http://sabredav.org/ns}email-address. * * The actual search should be a unicode-non-case-sensitive search. The * keys in searchProperties are the WebDAV property names, while the values * are the property values to search on. * * If multiple properties are being searched on, the search should be * AND'ed. * * This method should simply return a list of 'child names', which may be * used to call $this->getChild in the future. * * @param array $searchProperties * @return array */ public function searchPrincipals(array $searchProperties) { $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties); $r = array(); foreach ($result as $row) { list(, $r[]) = DAV\URLUtil::splitPath($row); } return $r; }
/** * Renames the node * * @param string $name The new name * @return void */ public function setName($name) { list($parentPath, ) = DAV\URLUtil::splitPath($this->path); list(, $newName) = DAV\URLUtil::splitPath($name); $newPath = $parentPath . '/' . $newName; // We're deleting the existing resourcedata, and recreating it // for the new path. $resourceData = $this->getResourceData(); $this->deleteResourceData(); rename($this->path, $newPath); $this->path = $newPath; $this->putResourceData($resourceData); }