@@ -17,70 +17,70 @@ |
||
17 | 17 | */ |
18 | 18 | class Plugin extends DAV\ServerPlugin { |
19 | 19 | |
20 | - /** |
|
21 | - * Reference to Server class |
|
22 | - * |
|
23 | - * @var Sabre\DAV\Server |
|
24 | - */ |
|
25 | - protected $server; |
|
26 | - |
|
27 | - /** |
|
28 | - * Initializes the plugin and registers event handles |
|
29 | - * |
|
30 | - * @param DAV\Server $server |
|
31 | - * @return void |
|
32 | - */ |
|
33 | - public function initialize(DAV\Server $server) { |
|
34 | - |
|
35 | - $this->server = $server; |
|
36 | - $this->server->on('method:GET', [$this, 'httpGet'], 90); |
|
37 | - |
|
38 | - } |
|
39 | - |
|
40 | - /** |
|
41 | - * 'beforeMethod' event handles. This event handles intercepts GET requests ending |
|
42 | - * with ?mount |
|
43 | - * |
|
44 | - * @param RequestInterface $request |
|
45 | - * @param ResponseInterface $response |
|
46 | - * @return bool |
|
47 | - */ |
|
48 | - public function httpGet(RequestInterface $request, ResponseInterface $response) { |
|
49 | - |
|
50 | - $queryParams = $request->getQueryParameters(); |
|
51 | - if (!array_key_exists('mount', $queryParams)) return; |
|
52 | - |
|
53 | - $currentUri = $request->getAbsoluteUrl(); |
|
54 | - |
|
55 | - // Stripping off everything after the ? |
|
56 | - list($currentUri) = explode('?', $currentUri); |
|
57 | - |
|
58 | - $this->davMount($response, $currentUri); |
|
59 | - |
|
60 | - // Returning false to break the event chain |
|
61 | - return false; |
|
62 | - |
|
63 | - } |
|
64 | - |
|
65 | - /** |
|
66 | - * Generates the davmount response |
|
67 | - * |
|
68 | - * @param ResponseInterface $response |
|
69 | - * @param string $uri absolute uri |
|
70 | - * @return void |
|
71 | - */ |
|
72 | - public function davMount(ResponseInterface $response, $uri) { |
|
73 | - |
|
74 | - $response->setStatus(200); |
|
75 | - $response->setHeader('Content-Type', 'application/davmount+xml'); |
|
76 | - ob_start(); |
|
77 | - echo '<?xml version="1.0"?>', "\n"; |
|
78 | - echo "<dm:mount xmlns:dm=\"http://purl.org/NET/webdav/mount\">\n"; |
|
79 | - echo " <dm:url>", htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "</dm:url>\n"; |
|
80 | - echo "</dm:mount>"; |
|
81 | - $response->setBody(ob_get_clean()); |
|
82 | - |
|
83 | - } |
|
20 | + /** |
|
21 | + * Reference to Server class |
|
22 | + * |
|
23 | + * @var Sabre\DAV\Server |
|
24 | + */ |
|
25 | + protected $server; |
|
26 | + |
|
27 | + /** |
|
28 | + * Initializes the plugin and registers event handles |
|
29 | + * |
|
30 | + * @param DAV\Server $server |
|
31 | + * @return void |
|
32 | + */ |
|
33 | + public function initialize(DAV\Server $server) { |
|
34 | + |
|
35 | + $this->server = $server; |
|
36 | + $this->server->on('method:GET', [$this, 'httpGet'], 90); |
|
37 | + |
|
38 | + } |
|
39 | + |
|
40 | + /** |
|
41 | + * 'beforeMethod' event handles. This event handles intercepts GET requests ending |
|
42 | + * with ?mount |
|
43 | + * |
|
44 | + * @param RequestInterface $request |
|
45 | + * @param ResponseInterface $response |
|
46 | + * @return bool |
|
47 | + */ |
|
48 | + public function httpGet(RequestInterface $request, ResponseInterface $response) { |
|
49 | + |
|
50 | + $queryParams = $request->getQueryParameters(); |
|
51 | + if (!array_key_exists('mount', $queryParams)) return; |
|
52 | + |
|
53 | + $currentUri = $request->getAbsoluteUrl(); |
|
54 | + |
|
55 | + // Stripping off everything after the ? |
|
56 | + list($currentUri) = explode('?', $currentUri); |
|
57 | + |
|
58 | + $this->davMount($response, $currentUri); |
|
59 | + |
|
60 | + // Returning false to break the event chain |
|
61 | + return false; |
|
62 | + |
|
63 | + } |
|
64 | + |
|
65 | + /** |
|
66 | + * Generates the davmount response |
|
67 | + * |
|
68 | + * @param ResponseInterface $response |
|
69 | + * @param string $uri absolute uri |
|
70 | + * @return void |
|
71 | + */ |
|
72 | + public function davMount(ResponseInterface $response, $uri) { |
|
73 | + |
|
74 | + $response->setStatus(200); |
|
75 | + $response->setHeader('Content-Type', 'application/davmount+xml'); |
|
76 | + ob_start(); |
|
77 | + echo '<?xml version="1.0"?>', "\n"; |
|
78 | + echo "<dm:mount xmlns:dm=\"http://purl.org/NET/webdav/mount\">\n"; |
|
79 | + echo " <dm:url>", htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "</dm:url>\n"; |
|
80 | + echo "</dm:mount>"; |
|
81 | + $response->setBody(ob_get_clean()); |
|
82 | + |
|
83 | + } |
|
84 | 84 | |
85 | 85 | |
86 | 86 | } |
@@ -17,72 +17,72 @@ |
||
17 | 17 | */ |
18 | 18 | interface ISyncCollection extends DAV\ICollection { |
19 | 19 | |
20 | - /** |
|
21 | - * This method returns the current sync-token for this collection. |
|
22 | - * This can be any string. |
|
23 | - * |
|
24 | - * If null is returned from this function, the plugin assumes there's no |
|
25 | - * sync information available. |
|
26 | - * |
|
27 | - * @return string|null |
|
28 | - */ |
|
29 | - public function getSyncToken(); |
|
20 | + /** |
|
21 | + * This method returns the current sync-token for this collection. |
|
22 | + * This can be any string. |
|
23 | + * |
|
24 | + * If null is returned from this function, the plugin assumes there's no |
|
25 | + * sync information available. |
|
26 | + * |
|
27 | + * @return string|null |
|
28 | + */ |
|
29 | + public function getSyncToken(); |
|
30 | 30 | |
31 | - /** |
|
32 | - * The getChanges method returns all the changes that have happened, since |
|
33 | - * the specified syncToken and the current collection. |
|
34 | - * |
|
35 | - * This function should return an array, such as the following: |
|
36 | - * |
|
37 | - * [ |
|
38 | - * 'syncToken' => 'The current synctoken', |
|
39 | - * 'added' => [ |
|
40 | - * 'new.txt', |
|
41 | - * ], |
|
42 | - * 'modified' => [ |
|
43 | - * 'modified.txt', |
|
44 | - * ], |
|
45 | - * 'deleted' => array( |
|
46 | - * 'foo.php.bak', |
|
47 | - * 'old.txt' |
|
48 | - * ) |
|
49 | - * ]; |
|
50 | - * |
|
51 | - * The syncToken property should reflect the *current* syncToken of the |
|
52 | - * collection, as reported getSyncToken(). This is needed here too, to |
|
53 | - * ensure the operation is atomic. |
|
54 | - * |
|
55 | - * If the syncToken is specified as null, this is an initial sync, and all |
|
56 | - * members should be reported. |
|
57 | - * |
|
58 | - * The modified property is an array of nodenames that have changed since |
|
59 | - * the last token. |
|
60 | - * |
|
61 | - * The deleted property is an array with nodenames, that have been deleted |
|
62 | - * from collection. |
|
63 | - * |
|
64 | - * The second argument is basically the 'depth' of the report. If it's 1, |
|
65 | - * you only have to report changes that happened only directly in immediate |
|
66 | - * descendants. If it's 2, it should also include changes from the nodes |
|
67 | - * below the child collections. (grandchildren) |
|
68 | - * |
|
69 | - * The third (optional) argument allows a client to specify how many |
|
70 | - * results should be returned at most. If the limit is not specified, it |
|
71 | - * should be treated as infinite. |
|
72 | - * |
|
73 | - * If the limit (infinite or not) is higher than you're willing to return, |
|
74 | - * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. |
|
75 | - * |
|
76 | - * If the syncToken is expired (due to data cleanup) or unknown, you must |
|
77 | - * return null. |
|
78 | - * |
|
79 | - * The limit is 'suggestive'. You are free to ignore it. |
|
80 | - * |
|
81 | - * @param string $syncToken |
|
82 | - * @param int $syncLevel |
|
83 | - * @param int $limit |
|
84 | - * @return array |
|
85 | - */ |
|
86 | - public function getChanges($syncToken, $syncLevel, $limit = null); |
|
31 | + /** |
|
32 | + * The getChanges method returns all the changes that have happened, since |
|
33 | + * the specified syncToken and the current collection. |
|
34 | + * |
|
35 | + * This function should return an array, such as the following: |
|
36 | + * |
|
37 | + * [ |
|
38 | + * 'syncToken' => 'The current synctoken', |
|
39 | + * 'added' => [ |
|
40 | + * 'new.txt', |
|
41 | + * ], |
|
42 | + * 'modified' => [ |
|
43 | + * 'modified.txt', |
|
44 | + * ], |
|
45 | + * 'deleted' => array( |
|
46 | + * 'foo.php.bak', |
|
47 | + * 'old.txt' |
|
48 | + * ) |
|
49 | + * ]; |
|
50 | + * |
|
51 | + * The syncToken property should reflect the *current* syncToken of the |
|
52 | + * collection, as reported getSyncToken(). This is needed here too, to |
|
53 | + * ensure the operation is atomic. |
|
54 | + * |
|
55 | + * If the syncToken is specified as null, this is an initial sync, and all |
|
56 | + * members should be reported. |
|
57 | + * |
|
58 | + * The modified property is an array of nodenames that have changed since |
|
59 | + * the last token. |
|
60 | + * |
|
61 | + * The deleted property is an array with nodenames, that have been deleted |
|
62 | + * from collection. |
|
63 | + * |
|
64 | + * The second argument is basically the 'depth' of the report. If it's 1, |
|
65 | + * you only have to report changes that happened only directly in immediate |
|
66 | + * descendants. If it's 2, it should also include changes from the nodes |
|
67 | + * below the child collections. (grandchildren) |
|
68 | + * |
|
69 | + * The third (optional) argument allows a client to specify how many |
|
70 | + * results should be returned at most. If the limit is not specified, it |
|
71 | + * should be treated as infinite. |
|
72 | + * |
|
73 | + * If the limit (infinite or not) is higher than you're willing to return, |
|
74 | + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. |
|
75 | + * |
|
76 | + * If the syncToken is expired (due to data cleanup) or unknown, you must |
|
77 | + * return null. |
|
78 | + * |
|
79 | + * The limit is 'suggestive'. You are free to ignore it. |
|
80 | + * |
|
81 | + * @param string $syncToken |
|
82 | + * @param int $syncLevel |
|
83 | + * @param int $limit |
|
84 | + * @return array |
|
85 | + */ |
|
86 | + public function getChanges($syncToken, $syncLevel, $limit = null); |
|
87 | 87 | |
88 | 88 | } |
@@ -20,258 +20,258 @@ |
||
20 | 20 | */ |
21 | 21 | class Plugin extends DAV\ServerPlugin { |
22 | 22 | |
23 | - /** |
|
24 | - * Reference to server object |
|
25 | - * |
|
26 | - * @var DAV\Server |
|
27 | - */ |
|
28 | - protected $server; |
|
29 | - |
|
30 | - const SYNCTOKEN_PREFIX = 'http://sabre.io/ns/sync/'; |
|
31 | - |
|
32 | - /** |
|
33 | - * Returns a plugin name. |
|
34 | - * |
|
35 | - * Using this name other plugins will be able to access other plugins |
|
36 | - * using \Sabre\DAV\Server::getPlugin |
|
37 | - * |
|
38 | - * @return string |
|
39 | - */ |
|
40 | - public function getPluginName() { |
|
41 | - |
|
42 | - return 'sync'; |
|
43 | - |
|
44 | - } |
|
45 | - |
|
46 | - /** |
|
47 | - * Initializes the plugin. |
|
48 | - * |
|
49 | - * This is when the plugin registers it's hooks. |
|
50 | - * |
|
51 | - * @param DAV\Server $server |
|
52 | - * @return void |
|
53 | - */ |
|
54 | - public function initialize(DAV\Server $server) { |
|
55 | - |
|
56 | - $this->server = $server; |
|
57 | - $server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport'; |
|
58 | - |
|
59 | - $self = $this; |
|
60 | - |
|
61 | - $server->on('report', function($reportName, $dom, $uri) use ($self) { |
|
62 | - |
|
63 | - if ($reportName === '{DAV:}sync-collection') { |
|
64 | - $this->server->transactionType = 'report-sync-collection'; |
|
65 | - $self->syncCollection($uri, $dom); |
|
66 | - return false; |
|
67 | - } |
|
68 | - |
|
69 | - }); |
|
70 | - |
|
71 | - $server->on('propFind', [$this, 'propFind']); |
|
72 | - $server->on('validateTokens', [$this, 'validateTokens']); |
|
73 | - |
|
74 | - } |
|
75 | - |
|
76 | - /** |
|
77 | - * Returns a list of reports this plugin supports. |
|
78 | - * |
|
79 | - * This will be used in the {DAV:}supported-report-set property. |
|
80 | - * Note that you still need to subscribe to the 'report' event to actually |
|
81 | - * implement them |
|
82 | - * |
|
83 | - * @param string $uri |
|
84 | - * @return array |
|
85 | - */ |
|
86 | - public function getSupportedReportSet($uri) { |
|
87 | - |
|
88 | - $node = $this->server->tree->getNodeForPath($uri); |
|
89 | - if ($node instanceof ISyncCollection && $node->getSyncToken()) { |
|
90 | - return [ |
|
91 | - '{DAV:}sync-collection', |
|
92 | - ]; |
|
93 | - } |
|
94 | - |
|
95 | - return []; |
|
96 | - |
|
97 | - } |
|
98 | - |
|
99 | - |
|
100 | - /** |
|
101 | - * This method handles the {DAV:}sync-collection HTTP REPORT. |
|
102 | - * |
|
103 | - * @param string $uri |
|
104 | - * @param SyncCollectionReport $report |
|
105 | - * @return void |
|
106 | - */ |
|
107 | - public function syncCollection($uri, SyncCollectionReport $report) { |
|
108 | - |
|
109 | - // Getting the data |
|
110 | - $node = $this->server->tree->getNodeForPath($uri); |
|
111 | - if (!$node instanceof ISyncCollection) { |
|
112 | - throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.'); |
|
113 | - } |
|
114 | - $token = $node->getSyncToken(); |
|
115 | - if (!$token) { |
|
116 | - throw new DAV\Exception\ReportNotSupported('No sync information is available at this node'); |
|
117 | - } |
|
118 | - |
|
119 | - $syncToken = $report->syncToken; |
|
120 | - if (!is_null($syncToken)) { |
|
121 | - // Sync-token must start with our prefix |
|
122 | - if (substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { |
|
123 | - throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); |
|
124 | - } |
|
125 | - |
|
126 | - $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX)); |
|
127 | - |
|
128 | - } |
|
129 | - $changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit); |
|
130 | - |
|
131 | - if (is_null($changeInfo)) { |
|
132 | - |
|
133 | - throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); |
|
134 | - |
|
135 | - } |
|
136 | - |
|
137 | - // Encoding the response |
|
138 | - $this->sendSyncCollectionResponse( |
|
139 | - $changeInfo['syncToken'], |
|
140 | - $uri, |
|
141 | - $changeInfo['added'], |
|
142 | - $changeInfo['modified'], |
|
143 | - $changeInfo['deleted'], |
|
144 | - $report->properties |
|
145 | - ); |
|
146 | - |
|
147 | - } |
|
148 | - |
|
149 | - /** |
|
150 | - * Sends the response to a sync-collection request. |
|
151 | - * |
|
152 | - * @param string $syncToken |
|
153 | - * @param string $collectionUrl |
|
154 | - * @param array $added |
|
155 | - * @param array $modified |
|
156 | - * @param array $deleted |
|
157 | - * @param array $properties |
|
158 | - * @return void |
|
159 | - */ |
|
160 | - protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) { |
|
161 | - |
|
162 | - |
|
163 | - $fullPaths = []; |
|
164 | - |
|
165 | - // Pre-fetching children, if this is possible. |
|
166 | - foreach (array_merge($added, $modified) as $item) { |
|
167 | - $fullPath = $collectionUrl . '/' . $item; |
|
168 | - $fullPaths[] = $fullPath; |
|
169 | - } |
|
170 | - |
|
171 | - $responses = []; |
|
172 | - foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { |
|
173 | - |
|
174 | - // The 'Property_Response' class is responsible for generating a |
|
175 | - // single {DAV:}response xml element. |
|
176 | - $responses[] = new DAV\Xml\Element\Response($fullPath, $props); |
|
177 | - |
|
178 | - } |
|
179 | - |
|
180 | - |
|
181 | - |
|
182 | - // Deleted items also show up as 'responses'. They have no properties, |
|
183 | - // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'. |
|
184 | - foreach ($deleted as $item) { |
|
185 | - |
|
186 | - $fullPath = $collectionUrl . '/' . $item; |
|
187 | - $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404); |
|
188 | - |
|
189 | - } |
|
190 | - $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX . $syncToken); |
|
191 | - |
|
192 | - $this->server->httpResponse->setStatus(207); |
|
193 | - $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); |
|
194 | - $this->server->httpResponse->setBody( |
|
195 | - $this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri()) |
|
196 | - ); |
|
197 | - |
|
198 | - } |
|
199 | - |
|
200 | - /** |
|
201 | - * This method is triggered whenever properties are requested for a node. |
|
202 | - * We intercept this to see if we must return a {DAV:}sync-token. |
|
203 | - * |
|
204 | - * @param DAV\PropFind $propFind |
|
205 | - * @param DAV\INode $node |
|
206 | - * @return void |
|
207 | - */ |
|
208 | - public function propFind(DAV\PropFind $propFind, DAV\INode $node) { |
|
209 | - |
|
210 | - $propFind->handle('{DAV:}sync-token', function() use ($node) { |
|
211 | - if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) { |
|
212 | - return; |
|
213 | - } |
|
214 | - return self::SYNCTOKEN_PREFIX . $token; |
|
215 | - }); |
|
216 | - |
|
217 | - } |
|
218 | - |
|
219 | - /** |
|
220 | - * The validateTokens event is triggered before every request. |
|
221 | - * |
|
222 | - * It's a moment where this plugin can check all the supplied lock tokens |
|
223 | - * in the If: header, and check if they are valid. |
|
224 | - * |
|
225 | - * @param RequestInterface $request |
|
226 | - * @param array $conditions |
|
227 | - * @return void |
|
228 | - */ |
|
229 | - public function validateTokens(RequestInterface $request, &$conditions) { |
|
230 | - |
|
231 | - foreach ($conditions as $kk => $condition) { |
|
232 | - |
|
233 | - foreach ($condition['tokens'] as $ii => $token) { |
|
234 | - |
|
235 | - // Sync-tokens must always start with our designated prefix. |
|
236 | - if (substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { |
|
237 | - continue; |
|
238 | - } |
|
239 | - |
|
240 | - // Checking if the token is a match. |
|
241 | - $node = $this->server->tree->getNodeForPath($condition['uri']); |
|
242 | - |
|
243 | - if ( |
|
244 | - $node instanceof ISyncCollection && |
|
245 | - $node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX)) |
|
246 | - ) { |
|
247 | - $conditions[$kk]['tokens'][$ii]['validToken'] = true; |
|
248 | - } |
|
249 | - |
|
250 | - } |
|
251 | - |
|
252 | - } |
|
253 | - |
|
254 | - } |
|
255 | - |
|
256 | - /** |
|
257 | - * Returns a bunch of meta-data about the plugin. |
|
258 | - * |
|
259 | - * Providing this information is optional, and is mainly displayed by the |
|
260 | - * Browser plugin. |
|
261 | - * |
|
262 | - * The description key in the returned array may contain html and will not |
|
263 | - * be sanitized. |
|
264 | - * |
|
265 | - * @return array |
|
266 | - */ |
|
267 | - public function getPluginInfo() { |
|
268 | - |
|
269 | - return [ |
|
270 | - 'name' => $this->getPluginName(), |
|
271 | - 'description' => 'Adds support for WebDAV Collection Sync (rfc6578)', |
|
272 | - 'link' => 'http://sabre.io/dav/sync/', |
|
273 | - ]; |
|
274 | - |
|
275 | - } |
|
23 | + /** |
|
24 | + * Reference to server object |
|
25 | + * |
|
26 | + * @var DAV\Server |
|
27 | + */ |
|
28 | + protected $server; |
|
29 | + |
|
30 | + const SYNCTOKEN_PREFIX = 'http://sabre.io/ns/sync/'; |
|
31 | + |
|
32 | + /** |
|
33 | + * Returns a plugin name. |
|
34 | + * |
|
35 | + * Using this name other plugins will be able to access other plugins |
|
36 | + * using \Sabre\DAV\Server::getPlugin |
|
37 | + * |
|
38 | + * @return string |
|
39 | + */ |
|
40 | + public function getPluginName() { |
|
41 | + |
|
42 | + return 'sync'; |
|
43 | + |
|
44 | + } |
|
45 | + |
|
46 | + /** |
|
47 | + * Initializes the plugin. |
|
48 | + * |
|
49 | + * This is when the plugin registers it's hooks. |
|
50 | + * |
|
51 | + * @param DAV\Server $server |
|
52 | + * @return void |
|
53 | + */ |
|
54 | + public function initialize(DAV\Server $server) { |
|
55 | + |
|
56 | + $this->server = $server; |
|
57 | + $server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport'; |
|
58 | + |
|
59 | + $self = $this; |
|
60 | + |
|
61 | + $server->on('report', function($reportName, $dom, $uri) use ($self) { |
|
62 | + |
|
63 | + if ($reportName === '{DAV:}sync-collection') { |
|
64 | + $this->server->transactionType = 'report-sync-collection'; |
|
65 | + $self->syncCollection($uri, $dom); |
|
66 | + return false; |
|
67 | + } |
|
68 | + |
|
69 | + }); |
|
70 | + |
|
71 | + $server->on('propFind', [$this, 'propFind']); |
|
72 | + $server->on('validateTokens', [$this, 'validateTokens']); |
|
73 | + |
|
74 | + } |
|
75 | + |
|
76 | + /** |
|
77 | + * Returns a list of reports this plugin supports. |
|
78 | + * |
|
79 | + * This will be used in the {DAV:}supported-report-set property. |
|
80 | + * Note that you still need to subscribe to the 'report' event to actually |
|
81 | + * implement them |
|
82 | + * |
|
83 | + * @param string $uri |
|
84 | + * @return array |
|
85 | + */ |
|
86 | + public function getSupportedReportSet($uri) { |
|
87 | + |
|
88 | + $node = $this->server->tree->getNodeForPath($uri); |
|
89 | + if ($node instanceof ISyncCollection && $node->getSyncToken()) { |
|
90 | + return [ |
|
91 | + '{DAV:}sync-collection', |
|
92 | + ]; |
|
93 | + } |
|
94 | + |
|
95 | + return []; |
|
96 | + |
|
97 | + } |
|
98 | + |
|
99 | + |
|
100 | + /** |
|
101 | + * This method handles the {DAV:}sync-collection HTTP REPORT. |
|
102 | + * |
|
103 | + * @param string $uri |
|
104 | + * @param SyncCollectionReport $report |
|
105 | + * @return void |
|
106 | + */ |
|
107 | + public function syncCollection($uri, SyncCollectionReport $report) { |
|
108 | + |
|
109 | + // Getting the data |
|
110 | + $node = $this->server->tree->getNodeForPath($uri); |
|
111 | + if (!$node instanceof ISyncCollection) { |
|
112 | + throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.'); |
|
113 | + } |
|
114 | + $token = $node->getSyncToken(); |
|
115 | + if (!$token) { |
|
116 | + throw new DAV\Exception\ReportNotSupported('No sync information is available at this node'); |
|
117 | + } |
|
118 | + |
|
119 | + $syncToken = $report->syncToken; |
|
120 | + if (!is_null($syncToken)) { |
|
121 | + // Sync-token must start with our prefix |
|
122 | + if (substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { |
|
123 | + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); |
|
124 | + } |
|
125 | + |
|
126 | + $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX)); |
|
127 | + |
|
128 | + } |
|
129 | + $changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit); |
|
130 | + |
|
131 | + if (is_null($changeInfo)) { |
|
132 | + |
|
133 | + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); |
|
134 | + |
|
135 | + } |
|
136 | + |
|
137 | + // Encoding the response |
|
138 | + $this->sendSyncCollectionResponse( |
|
139 | + $changeInfo['syncToken'], |
|
140 | + $uri, |
|
141 | + $changeInfo['added'], |
|
142 | + $changeInfo['modified'], |
|
143 | + $changeInfo['deleted'], |
|
144 | + $report->properties |
|
145 | + ); |
|
146 | + |
|
147 | + } |
|
148 | + |
|
149 | + /** |
|
150 | + * Sends the response to a sync-collection request. |
|
151 | + * |
|
152 | + * @param string $syncToken |
|
153 | + * @param string $collectionUrl |
|
154 | + * @param array $added |
|
155 | + * @param array $modified |
|
156 | + * @param array $deleted |
|
157 | + * @param array $properties |
|
158 | + * @return void |
|
159 | + */ |
|
160 | + protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) { |
|
161 | + |
|
162 | + |
|
163 | + $fullPaths = []; |
|
164 | + |
|
165 | + // Pre-fetching children, if this is possible. |
|
166 | + foreach (array_merge($added, $modified) as $item) { |
|
167 | + $fullPath = $collectionUrl . '/' . $item; |
|
168 | + $fullPaths[] = $fullPath; |
|
169 | + } |
|
170 | + |
|
171 | + $responses = []; |
|
172 | + foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { |
|
173 | + |
|
174 | + // The 'Property_Response' class is responsible for generating a |
|
175 | + // single {DAV:}response xml element. |
|
176 | + $responses[] = new DAV\Xml\Element\Response($fullPath, $props); |
|
177 | + |
|
178 | + } |
|
179 | + |
|
180 | + |
|
181 | + |
|
182 | + // Deleted items also show up as 'responses'. They have no properties, |
|
183 | + // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'. |
|
184 | + foreach ($deleted as $item) { |
|
185 | + |
|
186 | + $fullPath = $collectionUrl . '/' . $item; |
|
187 | + $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404); |
|
188 | + |
|
189 | + } |
|
190 | + $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX . $syncToken); |
|
191 | + |
|
192 | + $this->server->httpResponse->setStatus(207); |
|
193 | + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); |
|
194 | + $this->server->httpResponse->setBody( |
|
195 | + $this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri()) |
|
196 | + ); |
|
197 | + |
|
198 | + } |
|
199 | + |
|
200 | + /** |
|
201 | + * This method is triggered whenever properties are requested for a node. |
|
202 | + * We intercept this to see if we must return a {DAV:}sync-token. |
|
203 | + * |
|
204 | + * @param DAV\PropFind $propFind |
|
205 | + * @param DAV\INode $node |
|
206 | + * @return void |
|
207 | + */ |
|
208 | + public function propFind(DAV\PropFind $propFind, DAV\INode $node) { |
|
209 | + |
|
210 | + $propFind->handle('{DAV:}sync-token', function() use ($node) { |
|
211 | + if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) { |
|
212 | + return; |
|
213 | + } |
|
214 | + return self::SYNCTOKEN_PREFIX . $token; |
|
215 | + }); |
|
216 | + |
|
217 | + } |
|
218 | + |
|
219 | + /** |
|
220 | + * The validateTokens event is triggered before every request. |
|
221 | + * |
|
222 | + * It's a moment where this plugin can check all the supplied lock tokens |
|
223 | + * in the If: header, and check if they are valid. |
|
224 | + * |
|
225 | + * @param RequestInterface $request |
|
226 | + * @param array $conditions |
|
227 | + * @return void |
|
228 | + */ |
|
229 | + public function validateTokens(RequestInterface $request, &$conditions) { |
|
230 | + |
|
231 | + foreach ($conditions as $kk => $condition) { |
|
232 | + |
|
233 | + foreach ($condition['tokens'] as $ii => $token) { |
|
234 | + |
|
235 | + // Sync-tokens must always start with our designated prefix. |
|
236 | + if (substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { |
|
237 | + continue; |
|
238 | + } |
|
239 | + |
|
240 | + // Checking if the token is a match. |
|
241 | + $node = $this->server->tree->getNodeForPath($condition['uri']); |
|
242 | + |
|
243 | + if ( |
|
244 | + $node instanceof ISyncCollection && |
|
245 | + $node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX)) |
|
246 | + ) { |
|
247 | + $conditions[$kk]['tokens'][$ii]['validToken'] = true; |
|
248 | + } |
|
249 | + |
|
250 | + } |
|
251 | + |
|
252 | + } |
|
253 | + |
|
254 | + } |
|
255 | + |
|
256 | + /** |
|
257 | + * Returns a bunch of meta-data about the plugin. |
|
258 | + * |
|
259 | + * Providing this information is optional, and is mainly displayed by the |
|
260 | + * Browser plugin. |
|
261 | + * |
|
262 | + * The description key in the returned array may contain html and will not |
|
263 | + * be sanitized. |
|
264 | + * |
|
265 | + * @return array |
|
266 | + */ |
|
267 | + public function getPluginInfo() { |
|
268 | + |
|
269 | + return [ |
|
270 | + 'name' => $this->getPluginName(), |
|
271 | + 'description' => 'Adds support for WebDAV Collection Sync (rfc6578)', |
|
272 | + 'link' => 'http://sabre.io/dav/sync/', |
|
273 | + ]; |
|
274 | + |
|
275 | + } |
|
276 | 276 | |
277 | 277 | } |
@@ -14,30 +14,30 @@ |
||
14 | 14 | */ |
15 | 15 | interface IExtendedCollection extends ICollection { |
16 | 16 | |
17 | - /** |
|
18 | - * Creates a new collection. |
|
19 | - * |
|
20 | - * This method will receive a MkCol object with all the information about |
|
21 | - * the new collection that's being created. |
|
22 | - * |
|
23 | - * The MkCol object contains information about the resourceType of the new |
|
24 | - * collection. If you don't support the specified resourceType, you should |
|
25 | - * throw Exception\InvalidResourceType. |
|
26 | - * |
|
27 | - * The object also contains a list of WebDAV properties for the new |
|
28 | - * collection. |
|
29 | - * |
|
30 | - * You should call the handle() method on this object to specify exactly |
|
31 | - * which properties you are storing. This allows the system to figure out |
|
32 | - * exactly which properties you didn't store, which in turn allows other |
|
33 | - * plugins (such as the propertystorage plugin) to handle storing the |
|
34 | - * property for you. |
|
35 | - * |
|
36 | - * @param string $name |
|
37 | - * @param MkCol $mkCol |
|
38 | - * @throws Exception\InvalidResourceType |
|
39 | - * @return void |
|
40 | - */ |
|
41 | - public function createExtendedCollection($name, MkCol $mkCol); |
|
17 | + /** |
|
18 | + * Creates a new collection. |
|
19 | + * |
|
20 | + * This method will receive a MkCol object with all the information about |
|
21 | + * the new collection that's being created. |
|
22 | + * |
|
23 | + * The MkCol object contains information about the resourceType of the new |
|
24 | + * collection. If you don't support the specified resourceType, you should |
|
25 | + * throw Exception\InvalidResourceType. |
|
26 | + * |
|
27 | + * The object also contains a list of WebDAV properties for the new |
|
28 | + * collection. |
|
29 | + * |
|
30 | + * You should call the handle() method on this object to specify exactly |
|
31 | + * which properties you are storing. This allows the system to figure out |
|
32 | + * exactly which properties you didn't store, which in turn allows other |
|
33 | + * plugins (such as the propertystorage plugin) to handle storing the |
|
34 | + * property for you. |
|
35 | + * |
|
36 | + * @param string $name |
|
37 | + * @param MkCol $mkCol |
|
38 | + * @throws Exception\InvalidResourceType |
|
39 | + * @return void |
|
40 | + */ |
|
41 | + public function createExtendedCollection($name, MkCol $mkCol); |
|
42 | 42 | |
43 | 43 | } |
@@ -22,50 +22,50 @@ |
||
22 | 22 | */ |
23 | 23 | class MkCol extends PropPatch { |
24 | 24 | |
25 | - /** |
|
26 | - * A list of resource-types in clark-notation. |
|
27 | - * |
|
28 | - * @var array |
|
29 | - */ |
|
30 | - protected $resourceType; |
|
25 | + /** |
|
26 | + * A list of resource-types in clark-notation. |
|
27 | + * |
|
28 | + * @var array |
|
29 | + */ |
|
30 | + protected $resourceType; |
|
31 | 31 | |
32 | - /** |
|
33 | - * Creates the MKCOL object. |
|
34 | - * |
|
35 | - * @param string[] $resourceType List of resourcetype values. |
|
36 | - * @param array $mutations List of new properties values. |
|
37 | - */ |
|
38 | - public function __construct(array $resourceType, array $mutations) { |
|
32 | + /** |
|
33 | + * Creates the MKCOL object. |
|
34 | + * |
|
35 | + * @param string[] $resourceType List of resourcetype values. |
|
36 | + * @param array $mutations List of new properties values. |
|
37 | + */ |
|
38 | + public function __construct(array $resourceType, array $mutations) { |
|
39 | 39 | |
40 | - $this->resourceType = $resourceType; |
|
41 | - parent::__construct($mutations); |
|
40 | + $this->resourceType = $resourceType; |
|
41 | + parent::__construct($mutations); |
|
42 | 42 | |
43 | - } |
|
43 | + } |
|
44 | 44 | |
45 | - /** |
|
46 | - * Returns the resourcetype of the new collection. |
|
47 | - * |
|
48 | - * @return string[] |
|
49 | - */ |
|
50 | - public function getResourceType() { |
|
45 | + /** |
|
46 | + * Returns the resourcetype of the new collection. |
|
47 | + * |
|
48 | + * @return string[] |
|
49 | + */ |
|
50 | + public function getResourceType() { |
|
51 | 51 | |
52 | - return $this->resourceType; |
|
52 | + return $this->resourceType; |
|
53 | 53 | |
54 | - } |
|
54 | + } |
|
55 | 55 | |
56 | - /** |
|
57 | - * Returns true or false if the MKCOL operation has at least the specified |
|
58 | - * resource type. |
|
59 | - * |
|
60 | - * If the resourcetype is specified as an array, all resourcetypes are |
|
61 | - * checked. |
|
62 | - * |
|
63 | - * @param string|string[] $resourceType |
|
64 | - */ |
|
65 | - public function hasResourceType($resourceType) { |
|
56 | + /** |
|
57 | + * Returns true or false if the MKCOL operation has at least the specified |
|
58 | + * resource type. |
|
59 | + * |
|
60 | + * If the resourcetype is specified as an array, all resourcetypes are |
|
61 | + * checked. |
|
62 | + * |
|
63 | + * @param string|string[] $resourceType |
|
64 | + */ |
|
65 | + public function hasResourceType($resourceType) { |
|
66 | 66 | |
67 | - return count(array_diff((array)$resourceType, $this->resourceType)) === 0; |
|
67 | + return count(array_diff((array)$resourceType, $this->resourceType)) === 0; |
|
68 | 68 | |
69 | - } |
|
69 | + } |
|
70 | 70 | |
71 | 71 | } |
@@ -14,96 +14,96 @@ |
||
14 | 14 | */ |
15 | 15 | abstract class Collection extends Node implements ICollection { |
16 | 16 | |
17 | - /** |
|
18 | - * Returns a child object, by its name. |
|
19 | - * |
|
20 | - * This method makes use of the getChildren method to grab all the child |
|
21 | - * nodes, and compares the name. |
|
22 | - * Generally its wise to override this, as this can usually be optimized |
|
23 | - * |
|
24 | - * This method must throw Sabre\DAV\Exception\NotFound if the node does not |
|
25 | - * exist. |
|
26 | - * |
|
27 | - * @param string $name |
|
28 | - * @throws Exception\NotFound |
|
29 | - * @return INode |
|
30 | - */ |
|
31 | - public function getChild($name) { |
|
32 | - |
|
33 | - foreach ($this->getChildren() as $child) { |
|
34 | - |
|
35 | - if ($child->getName() === $name) return $child; |
|
36 | - |
|
37 | - } |
|
38 | - throw new Exception\NotFound('File not found: ' . $name); |
|
39 | - |
|
40 | - } |
|
41 | - |
|
42 | - /** |
|
43 | - * Checks is a child-node exists. |
|
44 | - * |
|
45 | - * It is generally a good idea to try and override this. Usually it can be optimized. |
|
46 | - * |
|
47 | - * @param string $name |
|
48 | - * @return bool |
|
49 | - */ |
|
50 | - public function childExists($name) { |
|
51 | - |
|
52 | - try { |
|
53 | - |
|
54 | - $this->getChild($name); |
|
55 | - return true; |
|
56 | - |
|
57 | - } catch (Exception\NotFound $e) { |
|
58 | - |
|
59 | - return false; |
|
60 | - |
|
61 | - } |
|
62 | - |
|
63 | - } |
|
64 | - |
|
65 | - /** |
|
66 | - * Creates a new file in the directory |
|
67 | - * |
|
68 | - * Data will either be supplied as a stream resource, or in certain cases |
|
69 | - * as a string. Keep in mind that you may have to support either. |
|
70 | - * |
|
71 | - * After succesful creation of the file, you may choose to return the ETag |
|
72 | - * of the new file here. |
|
73 | - * |
|
74 | - * The returned ETag must be surrounded by double-quotes (The quotes should |
|
75 | - * be part of the actual string). |
|
76 | - * |
|
77 | - * If you cannot accurately determine the ETag, you should not return it. |
|
78 | - * If you don't store the file exactly as-is (you're transforming it |
|
79 | - * somehow) you should also not return an ETag. |
|
80 | - * |
|
81 | - * This means that if a subsequent GET to this new file does not exactly |
|
82 | - * return the same contents of what was submitted here, you are strongly |
|
83 | - * recommended to omit the ETag. |
|
84 | - * |
|
85 | - * @param string $name Name of the file |
|
86 | - * @param resource|string $data Initial payload |
|
87 | - * @return null|string |
|
88 | - */ |
|
89 | - public function createFile($name, $data = null) { |
|
90 | - |
|
91 | - throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')'); |
|
92 | - |
|
93 | - } |
|
94 | - |
|
95 | - /** |
|
96 | - * Creates a new subdirectory |
|
97 | - * |
|
98 | - * @param string $name |
|
99 | - * @throws Exception\Forbidden |
|
100 | - * @return void |
|
101 | - */ |
|
102 | - public function createDirectory($name) { |
|
103 | - |
|
104 | - throw new Exception\Forbidden('Permission denied to create directory'); |
|
105 | - |
|
106 | - } |
|
17 | + /** |
|
18 | + * Returns a child object, by its name. |
|
19 | + * |
|
20 | + * This method makes use of the getChildren method to grab all the child |
|
21 | + * nodes, and compares the name. |
|
22 | + * Generally its wise to override this, as this can usually be optimized |
|
23 | + * |
|
24 | + * This method must throw Sabre\DAV\Exception\NotFound if the node does not |
|
25 | + * exist. |
|
26 | + * |
|
27 | + * @param string $name |
|
28 | + * @throws Exception\NotFound |
|
29 | + * @return INode |
|
30 | + */ |
|
31 | + public function getChild($name) { |
|
32 | + |
|
33 | + foreach ($this->getChildren() as $child) { |
|
34 | + |
|
35 | + if ($child->getName() === $name) return $child; |
|
36 | + |
|
37 | + } |
|
38 | + throw new Exception\NotFound('File not found: ' . $name); |
|
39 | + |
|
40 | + } |
|
41 | + |
|
42 | + /** |
|
43 | + * Checks is a child-node exists. |
|
44 | + * |
|
45 | + * It is generally a good idea to try and override this. Usually it can be optimized. |
|
46 | + * |
|
47 | + * @param string $name |
|
48 | + * @return bool |
|
49 | + */ |
|
50 | + public function childExists($name) { |
|
51 | + |
|
52 | + try { |
|
53 | + |
|
54 | + $this->getChild($name); |
|
55 | + return true; |
|
56 | + |
|
57 | + } catch (Exception\NotFound $e) { |
|
58 | + |
|
59 | + return false; |
|
60 | + |
|
61 | + } |
|
62 | + |
|
63 | + } |
|
64 | + |
|
65 | + /** |
|
66 | + * Creates a new file in the directory |
|
67 | + * |
|
68 | + * Data will either be supplied as a stream resource, or in certain cases |
|
69 | + * as a string. Keep in mind that you may have to support either. |
|
70 | + * |
|
71 | + * After succesful creation of the file, you may choose to return the ETag |
|
72 | + * of the new file here. |
|
73 | + * |
|
74 | + * The returned ETag must be surrounded by double-quotes (The quotes should |
|
75 | + * be part of the actual string). |
|
76 | + * |
|
77 | + * If you cannot accurately determine the ETag, you should not return it. |
|
78 | + * If you don't store the file exactly as-is (you're transforming it |
|
79 | + * somehow) you should also not return an ETag. |
|
80 | + * |
|
81 | + * This means that if a subsequent GET to this new file does not exactly |
|
82 | + * return the same contents of what was submitted here, you are strongly |
|
83 | + * recommended to omit the ETag. |
|
84 | + * |
|
85 | + * @param string $name Name of the file |
|
86 | + * @param resource|string $data Initial payload |
|
87 | + * @return null|string |
|
88 | + */ |
|
89 | + public function createFile($name, $data = null) { |
|
90 | + |
|
91 | + throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')'); |
|
92 | + |
|
93 | + } |
|
94 | + |
|
95 | + /** |
|
96 | + * Creates a new subdirectory |
|
97 | + * |
|
98 | + * @param string $name |
|
99 | + * @throws Exception\Forbidden |
|
100 | + * @return void |
|
101 | + */ |
|
102 | + public function createDirectory($name) { |
|
103 | + |
|
104 | + throw new Exception\Forbidden('Permission denied to create directory'); |
|
105 | + |
|
106 | + } |
|
107 | 107 | |
108 | 108 | |
109 | 109 | } |
@@ -32,266 +32,266 @@ |
||
32 | 32 | */ |
33 | 33 | class TemporaryFileFilterPlugin extends ServerPlugin { |
34 | 34 | |
35 | - /** |
|
36 | - * This is the list of patterns we intercept. |
|
37 | - * If new patterns are added, they must be valid patterns for preg_match. |
|
38 | - * |
|
39 | - * @var array |
|
40 | - */ |
|
41 | - public $temporaryFilePatterns = [ |
|
42 | - '/^\._(.*)$/', // OS/X resource forks |
|
43 | - '/^.DS_Store$/', // OS/X custom folder settings |
|
44 | - '/^desktop.ini$/', // Windows custom folder settings |
|
45 | - '/^Thumbs.db$/', // Windows thumbnail cache |
|
46 | - '/^.(.*).swp$/', // ViM temporary files |
|
47 | - '/^\.dat(.*)$/', // Smultron seems to create these |
|
48 | - '/^~lock.(.*)#$/', // Windows 7 lockfiles |
|
49 | - ]; |
|
50 | - |
|
51 | - /** |
|
52 | - * A reference to the main Server class |
|
53 | - * |
|
54 | - * @var Sabre\DAV\Server |
|
55 | - */ |
|
56 | - protected $server; |
|
57 | - |
|
58 | - /** |
|
59 | - * This is the directory where this plugin |
|
60 | - * will store it's files. |
|
61 | - * |
|
62 | - * @var string |
|
63 | - */ |
|
64 | - private $dataDir; |
|
65 | - |
|
66 | - /** |
|
67 | - * Creates the plugin. |
|
68 | - * |
|
69 | - * Make sure you specify a directory for your files. If you don't, we |
|
70 | - * will use PHP's directory for session-storage instead, and you might |
|
71 | - * not want that. |
|
72 | - * |
|
73 | - * @param string|null $dataDir |
|
74 | - */ |
|
75 | - public function __construct($dataDir = null) { |
|
76 | - |
|
77 | - if (!$dataDir) $dataDir = ini_get('session.save_path') . '/sabredav/'; |
|
78 | - if (!is_dir($dataDir)) mkdir($dataDir); |
|
79 | - $this->dataDir = $dataDir; |
|
80 | - |
|
81 | - } |
|
82 | - |
|
83 | - /** |
|
84 | - * Initialize the plugin |
|
85 | - * |
|
86 | - * This is called automatically be the Server class after this plugin is |
|
87 | - * added with Sabre\DAV\Server::addPlugin() |
|
88 | - * |
|
89 | - * @param Server $server |
|
90 | - * @return void |
|
91 | - */ |
|
92 | - public function initialize(Server $server) { |
|
93 | - |
|
94 | - $this->server = $server; |
|
95 | - $server->on('beforeMethod', [$this, 'beforeMethod']); |
|
96 | - $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); |
|
97 | - |
|
98 | - } |
|
99 | - |
|
100 | - /** |
|
101 | - * This method is called before any HTTP method handler |
|
102 | - * |
|
103 | - * This method intercepts any GET, DELETE, PUT and PROPFIND calls to |
|
104 | - * filenames that are known to match the 'temporary file' regex. |
|
105 | - * |
|
106 | - * @param RequestInterface $request |
|
107 | - * @param ResponseInterface $response |
|
108 | - * @return bool |
|
109 | - */ |
|
110 | - public function beforeMethod(RequestInterface $request, ResponseInterface $response) { |
|
111 | - |
|
112 | - if (!$tempLocation = $this->isTempFile($request->getPath())) |
|
113 | - return; |
|
114 | - |
|
115 | - switch ($request->getMethod()) { |
|
116 | - case 'GET' : |
|
117 | - return $this->httpGet($request, $response, $tempLocation); |
|
118 | - case 'PUT' : |
|
119 | - return $this->httpPut($request, $response, $tempLocation); |
|
120 | - case 'PROPFIND' : |
|
121 | - return $this->httpPropfind($request, $response, $tempLocation); |
|
122 | - case 'DELETE' : |
|
123 | - return $this->httpDelete($request, $response, $tempLocation); |
|
124 | - } |
|
125 | - return; |
|
126 | - |
|
127 | - } |
|
128 | - |
|
129 | - /** |
|
130 | - * This method is invoked if some subsystem creates a new file. |
|
131 | - * |
|
132 | - * This is used to deal with HTTP LOCK requests which create a new |
|
133 | - * file. |
|
134 | - * |
|
135 | - * @param string $uri |
|
136 | - * @param resource $data |
|
137 | - * @param DAV\ICollection $parentNode |
|
138 | - * @param bool $modified Should be set to true, if this event handler |
|
139 | - * changed &$data. |
|
140 | - * @return bool |
|
141 | - */ |
|
142 | - public function beforeCreateFile($uri, $data, $parent, $modified) { |
|
143 | - |
|
144 | - if ($tempPath = $this->isTempFile($uri)) { |
|
145 | - |
|
146 | - $hR = $this->server->httpResponse; |
|
147 | - $hR->setHeader('X-Sabre-Temp', 'true'); |
|
148 | - file_put_contents($tempPath, $data); |
|
149 | - return false; |
|
150 | - } |
|
151 | - return; |
|
152 | - |
|
153 | - } |
|
154 | - |
|
155 | - /** |
|
156 | - * This method will check if the url matches the temporary file pattern |
|
157 | - * if it does, it will return an path based on $this->dataDir for the |
|
158 | - * temporary file storage. |
|
159 | - * |
|
160 | - * @param string $path |
|
161 | - * @return bool|string |
|
162 | - */ |
|
163 | - protected function isTempFile($path) { |
|
164 | - |
|
165 | - // We're only interested in the basename. |
|
166 | - list(, $tempPath) = URLUtil::splitPath($path); |
|
167 | - |
|
168 | - foreach ($this->temporaryFilePatterns as $tempFile) { |
|
169 | - |
|
170 | - if (preg_match($tempFile, $tempPath)) { |
|
171 | - return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; |
|
172 | - } |
|
173 | - |
|
174 | - } |
|
175 | - |
|
176 | - return false; |
|
177 | - |
|
178 | - } |
|
179 | - |
|
180 | - |
|
181 | - /** |
|
182 | - * This method handles the GET method for temporary files. |
|
183 | - * If the file doesn't exist, it will return false which will kick in |
|
184 | - * the regular system for the GET method. |
|
185 | - * |
|
186 | - * @param RequestInterface $request |
|
187 | - * @param ResponseInterface $hR |
|
188 | - * @param string $tempLocation |
|
189 | - * @return bool |
|
190 | - */ |
|
191 | - public function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
192 | - |
|
193 | - if (!file_exists($tempLocation)) return; |
|
194 | - |
|
195 | - $hR->setHeader('Content-Type', 'application/octet-stream'); |
|
196 | - $hR->setHeader('Content-Length', filesize($tempLocation)); |
|
197 | - $hR->setHeader('X-Sabre-Temp', 'true'); |
|
198 | - $hR->setStatus(200); |
|
199 | - $hR->setBody(fopen($tempLocation, 'r')); |
|
200 | - return false; |
|
201 | - |
|
202 | - } |
|
203 | - |
|
204 | - /** |
|
205 | - * This method handles the PUT method. |
|
206 | - * |
|
207 | - * @param RequestInterface $request |
|
208 | - * @param ResponseInterface $hR |
|
209 | - * @param string $tempLocation |
|
210 | - * @return bool |
|
211 | - */ |
|
212 | - public function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
213 | - |
|
214 | - $hR->setHeader('X-Sabre-Temp', 'true'); |
|
215 | - |
|
216 | - $newFile = !file_exists($tempLocation); |
|
217 | - |
|
218 | - if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { |
|
219 | - throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); |
|
220 | - } |
|
221 | - |
|
222 | - file_put_contents($tempLocation, $this->server->httpRequest->getBody()); |
|
223 | - $hR->setStatus($newFile ? 201 : 200); |
|
224 | - return false; |
|
225 | - |
|
226 | - } |
|
227 | - |
|
228 | - /** |
|
229 | - * This method handles the DELETE method. |
|
230 | - * |
|
231 | - * If the file didn't exist, it will return false, which will make the |
|
232 | - * standard HTTP DELETE handler kick in. |
|
233 | - * |
|
234 | - * @param RequestInterface $request |
|
235 | - * @param ResponseInterface $hR |
|
236 | - * @param string $tempLocation |
|
237 | - * @return bool |
|
238 | - */ |
|
239 | - public function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
240 | - |
|
241 | - if (!file_exists($tempLocation)) return; |
|
242 | - |
|
243 | - unlink($tempLocation); |
|
244 | - $hR->setHeader('X-Sabre-Temp', 'true'); |
|
245 | - $hR->setStatus(204); |
|
246 | - return false; |
|
247 | - |
|
248 | - } |
|
249 | - |
|
250 | - /** |
|
251 | - * This method handles the PROPFIND method. |
|
252 | - * |
|
253 | - * It's a very lazy method, it won't bother checking the request body |
|
254 | - * for which properties were requested, and just sends back a default |
|
255 | - * set of properties. |
|
256 | - * |
|
257 | - * @param RequestInterface $request |
|
258 | - * @param ResponseInterface $hR |
|
259 | - * @param string $tempLocation |
|
260 | - * @return bool |
|
261 | - */ |
|
262 | - public function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
263 | - |
|
264 | - if (!file_exists($tempLocation)) return; |
|
265 | - |
|
266 | - $hR->setHeader('X-Sabre-Temp', 'true'); |
|
267 | - $hR->setStatus(207); |
|
268 | - $hR->setHeader('Content-Type', 'application/xml; charset=utf-8'); |
|
269 | - |
|
270 | - $properties = [ |
|
271 | - 'href' => $request->getPath(), |
|
272 | - 200 => [ |
|
273 | - '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), |
|
274 | - '{DAV:}getcontentlength' => filesize($tempLocation), |
|
275 | - '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), |
|
276 | - '{' . Server::NS_SABREDAV . '}tempFile' => true, |
|
277 | - |
|
278 | - ], |
|
279 | - ]; |
|
280 | - |
|
281 | - $data = $this->server->generateMultiStatus([$properties]); |
|
282 | - $hR->setBody($data); |
|
283 | - return false; |
|
284 | - |
|
285 | - } |
|
286 | - |
|
287 | - |
|
288 | - /** |
|
289 | - * This method returns the directory where the temporary files should be stored. |
|
290 | - * |
|
291 | - * @return string |
|
292 | - */ |
|
293 | - protected function getDataDir() |
|
294 | - { |
|
295 | - return $this->dataDir; |
|
296 | - } |
|
35 | + /** |
|
36 | + * This is the list of patterns we intercept. |
|
37 | + * If new patterns are added, they must be valid patterns for preg_match. |
|
38 | + * |
|
39 | + * @var array |
|
40 | + */ |
|
41 | + public $temporaryFilePatterns = [ |
|
42 | + '/^\._(.*)$/', // OS/X resource forks |
|
43 | + '/^.DS_Store$/', // OS/X custom folder settings |
|
44 | + '/^desktop.ini$/', // Windows custom folder settings |
|
45 | + '/^Thumbs.db$/', // Windows thumbnail cache |
|
46 | + '/^.(.*).swp$/', // ViM temporary files |
|
47 | + '/^\.dat(.*)$/', // Smultron seems to create these |
|
48 | + '/^~lock.(.*)#$/', // Windows 7 lockfiles |
|
49 | + ]; |
|
50 | + |
|
51 | + /** |
|
52 | + * A reference to the main Server class |
|
53 | + * |
|
54 | + * @var Sabre\DAV\Server |
|
55 | + */ |
|
56 | + protected $server; |
|
57 | + |
|
58 | + /** |
|
59 | + * This is the directory where this plugin |
|
60 | + * will store it's files. |
|
61 | + * |
|
62 | + * @var string |
|
63 | + */ |
|
64 | + private $dataDir; |
|
65 | + |
|
66 | + /** |
|
67 | + * Creates the plugin. |
|
68 | + * |
|
69 | + * Make sure you specify a directory for your files. If you don't, we |
|
70 | + * will use PHP's directory for session-storage instead, and you might |
|
71 | + * not want that. |
|
72 | + * |
|
73 | + * @param string|null $dataDir |
|
74 | + */ |
|
75 | + public function __construct($dataDir = null) { |
|
76 | + |
|
77 | + if (!$dataDir) $dataDir = ini_get('session.save_path') . '/sabredav/'; |
|
78 | + if (!is_dir($dataDir)) mkdir($dataDir); |
|
79 | + $this->dataDir = $dataDir; |
|
80 | + |
|
81 | + } |
|
82 | + |
|
83 | + /** |
|
84 | + * Initialize the plugin |
|
85 | + * |
|
86 | + * This is called automatically be the Server class after this plugin is |
|
87 | + * added with Sabre\DAV\Server::addPlugin() |
|
88 | + * |
|
89 | + * @param Server $server |
|
90 | + * @return void |
|
91 | + */ |
|
92 | + public function initialize(Server $server) { |
|
93 | + |
|
94 | + $this->server = $server; |
|
95 | + $server->on('beforeMethod', [$this, 'beforeMethod']); |
|
96 | + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); |
|
97 | + |
|
98 | + } |
|
99 | + |
|
100 | + /** |
|
101 | + * This method is called before any HTTP method handler |
|
102 | + * |
|
103 | + * This method intercepts any GET, DELETE, PUT and PROPFIND calls to |
|
104 | + * filenames that are known to match the 'temporary file' regex. |
|
105 | + * |
|
106 | + * @param RequestInterface $request |
|
107 | + * @param ResponseInterface $response |
|
108 | + * @return bool |
|
109 | + */ |
|
110 | + public function beforeMethod(RequestInterface $request, ResponseInterface $response) { |
|
111 | + |
|
112 | + if (!$tempLocation = $this->isTempFile($request->getPath())) |
|
113 | + return; |
|
114 | + |
|
115 | + switch ($request->getMethod()) { |
|
116 | + case 'GET' : |
|
117 | + return $this->httpGet($request, $response, $tempLocation); |
|
118 | + case 'PUT' : |
|
119 | + return $this->httpPut($request, $response, $tempLocation); |
|
120 | + case 'PROPFIND' : |
|
121 | + return $this->httpPropfind($request, $response, $tempLocation); |
|
122 | + case 'DELETE' : |
|
123 | + return $this->httpDelete($request, $response, $tempLocation); |
|
124 | + } |
|
125 | + return; |
|
126 | + |
|
127 | + } |
|
128 | + |
|
129 | + /** |
|
130 | + * This method is invoked if some subsystem creates a new file. |
|
131 | + * |
|
132 | + * This is used to deal with HTTP LOCK requests which create a new |
|
133 | + * file. |
|
134 | + * |
|
135 | + * @param string $uri |
|
136 | + * @param resource $data |
|
137 | + * @param DAV\ICollection $parentNode |
|
138 | + * @param bool $modified Should be set to true, if this event handler |
|
139 | + * changed &$data. |
|
140 | + * @return bool |
|
141 | + */ |
|
142 | + public function beforeCreateFile($uri, $data, $parent, $modified) { |
|
143 | + |
|
144 | + if ($tempPath = $this->isTempFile($uri)) { |
|
145 | + |
|
146 | + $hR = $this->server->httpResponse; |
|
147 | + $hR->setHeader('X-Sabre-Temp', 'true'); |
|
148 | + file_put_contents($tempPath, $data); |
|
149 | + return false; |
|
150 | + } |
|
151 | + return; |
|
152 | + |
|
153 | + } |
|
154 | + |
|
155 | + /** |
|
156 | + * This method will check if the url matches the temporary file pattern |
|
157 | + * if it does, it will return an path based on $this->dataDir for the |
|
158 | + * temporary file storage. |
|
159 | + * |
|
160 | + * @param string $path |
|
161 | + * @return bool|string |
|
162 | + */ |
|
163 | + protected function isTempFile($path) { |
|
164 | + |
|
165 | + // We're only interested in the basename. |
|
166 | + list(, $tempPath) = URLUtil::splitPath($path); |
|
167 | + |
|
168 | + foreach ($this->temporaryFilePatterns as $tempFile) { |
|
169 | + |
|
170 | + if (preg_match($tempFile, $tempPath)) { |
|
171 | + return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; |
|
172 | + } |
|
173 | + |
|
174 | + } |
|
175 | + |
|
176 | + return false; |
|
177 | + |
|
178 | + } |
|
179 | + |
|
180 | + |
|
181 | + /** |
|
182 | + * This method handles the GET method for temporary files. |
|
183 | + * If the file doesn't exist, it will return false which will kick in |
|
184 | + * the regular system for the GET method. |
|
185 | + * |
|
186 | + * @param RequestInterface $request |
|
187 | + * @param ResponseInterface $hR |
|
188 | + * @param string $tempLocation |
|
189 | + * @return bool |
|
190 | + */ |
|
191 | + public function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
192 | + |
|
193 | + if (!file_exists($tempLocation)) return; |
|
194 | + |
|
195 | + $hR->setHeader('Content-Type', 'application/octet-stream'); |
|
196 | + $hR->setHeader('Content-Length', filesize($tempLocation)); |
|
197 | + $hR->setHeader('X-Sabre-Temp', 'true'); |
|
198 | + $hR->setStatus(200); |
|
199 | + $hR->setBody(fopen($tempLocation, 'r')); |
|
200 | + return false; |
|
201 | + |
|
202 | + } |
|
203 | + |
|
204 | + /** |
|
205 | + * This method handles the PUT method. |
|
206 | + * |
|
207 | + * @param RequestInterface $request |
|
208 | + * @param ResponseInterface $hR |
|
209 | + * @param string $tempLocation |
|
210 | + * @return bool |
|
211 | + */ |
|
212 | + public function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
213 | + |
|
214 | + $hR->setHeader('X-Sabre-Temp', 'true'); |
|
215 | + |
|
216 | + $newFile = !file_exists($tempLocation); |
|
217 | + |
|
218 | + if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { |
|
219 | + throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); |
|
220 | + } |
|
221 | + |
|
222 | + file_put_contents($tempLocation, $this->server->httpRequest->getBody()); |
|
223 | + $hR->setStatus($newFile ? 201 : 200); |
|
224 | + return false; |
|
225 | + |
|
226 | + } |
|
227 | + |
|
228 | + /** |
|
229 | + * This method handles the DELETE method. |
|
230 | + * |
|
231 | + * If the file didn't exist, it will return false, which will make the |
|
232 | + * standard HTTP DELETE handler kick in. |
|
233 | + * |
|
234 | + * @param RequestInterface $request |
|
235 | + * @param ResponseInterface $hR |
|
236 | + * @param string $tempLocation |
|
237 | + * @return bool |
|
238 | + */ |
|
239 | + public function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
240 | + |
|
241 | + if (!file_exists($tempLocation)) return; |
|
242 | + |
|
243 | + unlink($tempLocation); |
|
244 | + $hR->setHeader('X-Sabre-Temp', 'true'); |
|
245 | + $hR->setStatus(204); |
|
246 | + return false; |
|
247 | + |
|
248 | + } |
|
249 | + |
|
250 | + /** |
|
251 | + * This method handles the PROPFIND method. |
|
252 | + * |
|
253 | + * It's a very lazy method, it won't bother checking the request body |
|
254 | + * for which properties were requested, and just sends back a default |
|
255 | + * set of properties. |
|
256 | + * |
|
257 | + * @param RequestInterface $request |
|
258 | + * @param ResponseInterface $hR |
|
259 | + * @param string $tempLocation |
|
260 | + * @return bool |
|
261 | + */ |
|
262 | + public function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) { |
|
263 | + |
|
264 | + if (!file_exists($tempLocation)) return; |
|
265 | + |
|
266 | + $hR->setHeader('X-Sabre-Temp', 'true'); |
|
267 | + $hR->setStatus(207); |
|
268 | + $hR->setHeader('Content-Type', 'application/xml; charset=utf-8'); |
|
269 | + |
|
270 | + $properties = [ |
|
271 | + 'href' => $request->getPath(), |
|
272 | + 200 => [ |
|
273 | + '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), |
|
274 | + '{DAV:}getcontentlength' => filesize($tempLocation), |
|
275 | + '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), |
|
276 | + '{' . Server::NS_SABREDAV . '}tempFile' => true, |
|
277 | + |
|
278 | + ], |
|
279 | + ]; |
|
280 | + |
|
281 | + $data = $this->server->generateMultiStatus([$properties]); |
|
282 | + $hR->setBody($data); |
|
283 | + return false; |
|
284 | + |
|
285 | + } |
|
286 | + |
|
287 | + |
|
288 | + /** |
|
289 | + * This method returns the directory where the temporary files should be stored. |
|
290 | + * |
|
291 | + * @return string |
|
292 | + */ |
|
293 | + protected function getDataDir() |
|
294 | + { |
|
295 | + return $this->dataDir; |
|
296 | + } |
|
297 | 297 | } |
@@ -15,12 +15,12 @@ |
||
15 | 15 | */ |
16 | 16 | interface IQuota extends ICollection { |
17 | 17 | |
18 | - /** |
|
19 | - * Returns the quota information |
|
20 | - * |
|
21 | - * This method MUST return an array with 2 values, the first being the total used space, |
|
22 | - * the second the available space (in bytes) |
|
23 | - */ |
|
24 | - public function getQuotaInfo(); |
|
18 | + /** |
|
19 | + * Returns the quota information |
|
20 | + * |
|
21 | + * This method MUST return an array with 2 values, the first being the total used space, |
|
22 | + * the second the available space (in bytes) |
|
23 | + */ |
|
24 | + public function getQuotaInfo(); |
|
25 | 25 | |
26 | 26 | } |
@@ -15,67 +15,67 @@ |
||
15 | 15 | */ |
16 | 16 | interface IFile extends INode { |
17 | 17 | |
18 | - /** |
|
19 | - * Replaces the contents of the file. |
|
20 | - * |
|
21 | - * The data argument is a readable stream resource. |
|
22 | - * |
|
23 | - * After a succesful put operation, you may choose to return an ETag. The |
|
24 | - * etag must always be surrounded by double-quotes. These quotes must |
|
25 | - * appear in the actual string you're returning. |
|
26 | - * |
|
27 | - * Clients may use the ETag from a PUT request to later on make sure that |
|
28 | - * when they update the file, the contents haven't changed in the mean |
|
29 | - * time. |
|
30 | - * |
|
31 | - * If you don't plan to store the file byte-by-byte, and you return a |
|
32 | - * different object on a subsequent GET you are strongly recommended to not |
|
33 | - * return an ETag, and just return null. |
|
34 | - * |
|
35 | - * @param resource $data |
|
36 | - * @return string|null |
|
37 | - */ |
|
38 | - public function put($data); |
|
18 | + /** |
|
19 | + * Replaces the contents of the file. |
|
20 | + * |
|
21 | + * The data argument is a readable stream resource. |
|
22 | + * |
|
23 | + * After a succesful put operation, you may choose to return an ETag. The |
|
24 | + * etag must always be surrounded by double-quotes. These quotes must |
|
25 | + * appear in the actual string you're returning. |
|
26 | + * |
|
27 | + * Clients may use the ETag from a PUT request to later on make sure that |
|
28 | + * when they update the file, the contents haven't changed in the mean |
|
29 | + * time. |
|
30 | + * |
|
31 | + * If you don't plan to store the file byte-by-byte, and you return a |
|
32 | + * different object on a subsequent GET you are strongly recommended to not |
|
33 | + * return an ETag, and just return null. |
|
34 | + * |
|
35 | + * @param resource $data |
|
36 | + * @return string|null |
|
37 | + */ |
|
38 | + public function put($data); |
|
39 | 39 | |
40 | - /** |
|
41 | - * Returns the data |
|
42 | - * |
|
43 | - * This method may either return a string or a readable stream resource |
|
44 | - * |
|
45 | - * @return mixed |
|
46 | - */ |
|
47 | - public function get(); |
|
40 | + /** |
|
41 | + * Returns the data |
|
42 | + * |
|
43 | + * This method may either return a string or a readable stream resource |
|
44 | + * |
|
45 | + * @return mixed |
|
46 | + */ |
|
47 | + public function get(); |
|
48 | 48 | |
49 | - /** |
|
50 | - * Returns the mime-type for a file |
|
51 | - * |
|
52 | - * If null is returned, we'll assume application/octet-stream |
|
53 | - * |
|
54 | - * @return string|null |
|
55 | - */ |
|
56 | - public function getContentType(); |
|
49 | + /** |
|
50 | + * Returns the mime-type for a file |
|
51 | + * |
|
52 | + * If null is returned, we'll assume application/octet-stream |
|
53 | + * |
|
54 | + * @return string|null |
|
55 | + */ |
|
56 | + public function getContentType(); |
|
57 | 57 | |
58 | - /** |
|
59 | - * Returns the ETag for a file |
|
60 | - * |
|
61 | - * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. |
|
62 | - * |
|
63 | - * Return null if the ETag can not effectively be determined. |
|
64 | - * |
|
65 | - * The ETag must be surrounded by double-quotes, so something like this |
|
66 | - * would make a valid ETag: |
|
67 | - * |
|
68 | - * return '"someetag"'; |
|
69 | - * |
|
70 | - * @return string|null |
|
71 | - */ |
|
72 | - public function getETag(); |
|
58 | + /** |
|
59 | + * Returns the ETag for a file |
|
60 | + * |
|
61 | + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. |
|
62 | + * |
|
63 | + * Return null if the ETag can not effectively be determined. |
|
64 | + * |
|
65 | + * The ETag must be surrounded by double-quotes, so something like this |
|
66 | + * would make a valid ETag: |
|
67 | + * |
|
68 | + * return '"someetag"'; |
|
69 | + * |
|
70 | + * @return string|null |
|
71 | + */ |
|
72 | + public function getETag(); |
|
73 | 73 | |
74 | - /** |
|
75 | - * Returns the size of the node, in bytes |
|
76 | - * |
|
77 | - * @return int |
|
78 | - */ |
|
79 | - public function getSize(); |
|
74 | + /** |
|
75 | + * Returns the size of the node, in bytes |
|
76 | + * |
|
77 | + * @return int |
|
78 | + */ |
|
79 | + public function getSize(); |
|
80 | 80 | |
81 | 81 | } |