1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs2.provider.webdav4;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.net.HttpURLConnection;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.commons.vfs2.FileContentInfoFactory;
29 import org.apache.commons.vfs2.FileNotFolderException;
30 import org.apache.commons.vfs2.FileNotFoundException;
31 import org.apache.commons.vfs2.FileObject;
32 import org.apache.commons.vfs2.FileSystemException;
33 import org.apache.commons.vfs2.FileType;
34 import org.apache.commons.vfs2.NameScope;
35 import org.apache.commons.vfs2.provider.AbstractFileName;
36 import org.apache.commons.vfs2.provider.DefaultFileContent;
37 import org.apache.commons.vfs2.provider.GenericURLFileName;
38 import org.apache.commons.vfs2.provider.http4.Http4FileObject;
39 import org.apache.commons.vfs2.util.FileObjectUtils;
40 import org.apache.commons.vfs2.util.MonitorOutputStream;
41 import org.apache.http.HttpEntity;
42 import org.apache.http.HttpResponse;
43 import org.apache.http.HttpStatus;
44 import org.apache.http.client.methods.CloseableHttpResponse;
45 import org.apache.http.client.methods.HttpPut;
46 import org.apache.http.client.methods.HttpUriRequest;
47 import org.apache.http.client.utils.DateUtils;
48 import org.apache.http.entity.ByteArrayEntity;
49 import org.apache.http.entity.ContentType;
50 import org.apache.http.util.EntityUtils;
51 import org.apache.jackrabbit.webdav.DavConstants;
52 import org.apache.jackrabbit.webdav.DavException;
53 import org.apache.jackrabbit.webdav.MultiStatus;
54 import org.apache.jackrabbit.webdav.MultiStatusResponse;
55 import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest;
56 import org.apache.jackrabbit.webdav.client.methods.HttpCheckin;
57 import org.apache.jackrabbit.webdav.client.methods.HttpCheckout;
58 import org.apache.jackrabbit.webdav.client.methods.HttpDelete;
59 import org.apache.jackrabbit.webdav.client.methods.HttpMkcol;
60 import org.apache.jackrabbit.webdav.client.methods.HttpMove;
61 import org.apache.jackrabbit.webdav.client.methods.HttpPropfind;
62 import org.apache.jackrabbit.webdav.client.methods.HttpProppatch;
63 import org.apache.jackrabbit.webdav.client.methods.HttpVersionControl;
64 import org.apache.jackrabbit.webdav.property.DavProperty;
65 import org.apache.jackrabbit.webdav.property.DavPropertyName;
66 import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
67 import org.apache.jackrabbit.webdav.property.DavPropertySet;
68 import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
69 import org.apache.jackrabbit.webdav.version.DeltaVConstants;
70 import org.apache.jackrabbit.webdav.version.VersionControlledResource;
71 import org.apache.jackrabbit.webdav.xml.Namespace;
72 import org.w3c.dom.Node;
73
74
75
76
77
78
79 public class Webdav4FileObject extends Http4FileObject<Webdav4FileSystem> {
80
81
82
83
84
85
86
87 private class WebdavOutputStream extends MonitorOutputStream {
88 private final Webdav4FileObject file;
89
90 WebdavOutputStream(final Webdav4FileObject file) {
91 super(new ByteArrayOutputStream());
92 this.file = file;
93 }
94
95 private boolean createVersion(final String urlStr) {
96 try {
97 final HttpVersionControl request = setupRequest(new HttpVersionControl(urlStr));
98
99 try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
100 return true;
101 }
102 } catch (final Exception ex) {
103 return false;
104 }
105 }
106
107
108
109
110 @Override
111 protected void onClose() throws IOException {
112 final HttpEntity entity = new ByteArrayEntity(((ByteArrayOutputStream) out).toByteArray());
113 final GenericURLFileName fileName = (GenericURLFileName) getName();
114 final String urlStr = toUrlString(fileName);
115 if (builder.isVersioning(getFileSystem().getFileSystemOptions())) {
116 DavPropertySet set = null;
117 boolean fileExists = true;
118 boolean isCheckedIn = true;
119 try {
120 set = getPropertyNames(fileName);
121 } catch (final FileNotFoundException fnfe) {
122 fileExists = false;
123 }
124 if (fileExists && set != null) {
125 if (set.contains(VersionControlledResource.CHECKED_OUT)) {
126 isCheckedIn = false;
127 } else if (!set.contains(VersionControlledResource.CHECKED_IN)) {
128 DavProperty<?> prop = set.get(VersionControlledResource.AUTO_VERSION);
129 if (prop != null) {
130 prop = getProperty(fileName, VersionControlledResource.AUTO_VERSION);
131 if (DeltaVConstants.XML_CHECKOUT_CHECKIN.equals(prop.getValue())) {
132 createVersion(urlStr);
133 }
134 }
135 }
136 }
137 if (fileExists && isCheckedIn) {
138 try {
139 final HttpCheckout request = setupRequest(new HttpCheckout(urlStr));
140
141 try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
142 isCheckedIn = false;
143 }
144 } catch (final FileSystemException ex) {
145 log(ex);
146 }
147 }
148
149 try {
150 final HttpPut request = new HttpPut(urlStr);
151 request.setEntity(entity);
152 setupRequest(request);
153
154 try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
155 setUserName(fileName, urlStr);
156 }
157 } catch (final FileSystemException ex) {
158 if (!isCheckedIn) {
159 try {
160 final HttpCheckin request = new HttpCheckin(urlStr);
161 setupRequest(request);
162
163 try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
164 isCheckedIn = true;
165 }
166 } catch (final Exception e) {
167
168 log(e);
169 }
170 throw ex;
171 }
172 }
173 if (!fileExists) {
174 createVersion(urlStr);
175 try {
176 final DavPropertySet props = getPropertyNames(fileName);
177 isCheckedIn = !props.contains(VersionControlledResource.CHECKED_OUT);
178 } catch (final FileNotFoundException fnfe) {
179 log(fnfe);
180 }
181 }
182 if (!isCheckedIn) {
183 final HttpCheckin request = new HttpCheckin(urlStr);
184 setupRequest(request);
185
186 try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
187 isCheckedIn = true;
188 }
189 }
190 } else {
191 final HttpPut request = new HttpPut(urlStr);
192 request.setEntity(entity);
193 setupRequest(request);
194
195 try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
196 try {
197 setUserName(fileName, urlStr);
198 } catch (final IOException e) {
199
200 log(e);
201 }
202 }
203 }
204 ((DefaultFileContent) file.getContent()).resetAttributes();
205 }
206
207 private void setUserName(final GenericURLFileName fileName, final String urlStr) throws IOException {
208 final DavPropertySet setProperties = new DavPropertySet();
209 final DavPropertyNameSet removeProperties = new DavPropertyNameSet();
210 String name = builder.getCreatorName(getFileSystem().getFileSystemOptions());
211 final String userName = fileName.getUserName();
212 if (name == null) {
213 name = userName;
214 } else if (userName != null) {
215 final String comment = "Modified by user " + userName;
216 setProperties.add(new DefaultDavProperty<>(DeltaVConstants.COMMENT, comment));
217 }
218 setProperties.add(new DefaultDavProperty<>(DeltaVConstants.CREATOR_DISPLAYNAME, name));
219 final HttpProppatch request = setupRequest(new HttpProppatch(urlStr, setProperties, removeProperties));
220
221 try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
222
223 request.succeeded(res);
224 }
225 }
226 }
227
228
229 public static final DavPropertyName RESPONSE_CHARSET = DavPropertyName.create("response-charset");
230
231
232
233
234 private static final Webdav4FileObject[] EMPTY_ARRAY = {};
235
236
237 private final Webdav4FileSystemConfigBuilder builder;
238
239
240
241
242
243
244
245
246 protected Webdav4FileObject(final AbstractFileName name, final Webdav4FileSystem fileSystem)
247 throws FileSystemException {
248 this(name, fileSystem, Webdav4FileSystemConfigBuilder.getInstance());
249 }
250
251
252
253
254
255
256
257
258
259 protected Webdav4FileObject(final AbstractFileName name, final Webdav4FileSystem fileSystem,
260 final Webdav4FileSystemConfigBuilder builder) throws FileSystemException {
261 super(name, fileSystem, builder);
262 this.builder = builder;
263 }
264
265
266
267
268 @Override
269 protected void doCreateFolder() throws Exception {
270 final HttpMkcol request = setupRequest(new HttpMkcol(toUrlString((GenericURLFileName) getName())));
271 try {
272
273 try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
274
275 request.succeeded(res);
276 }
277 } catch (final FileSystemException fse) {
278 throw new FileSystemException("vfs.provider.webdav/create-collection.error", getName(), fse);
279 }
280 }
281
282
283
284
285 @Override
286 protected void doDelete() throws Exception {
287 final HttpDelete request = setupRequest(new HttpDelete(toUrlString((GenericURLFileName) getName())));
288
289 try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
290
291 request.succeeded(res);
292 }
293 }
294
295
296
297
298 @Override
299 protected Map<String, Object> doGetAttributes() throws Exception {
300 final Map<String, Object> attributes = new HashMap<>();
301 try {
302 final GenericURLFileName fileName = (GenericURLFileName) getName();
303 DavPropertySet properties = getProperties(fileName, DavConstants.PROPFIND_ALL_PROP,
304 new DavPropertyNameSet(), false);
305 for (final DavProperty<?> property : properties) {
306 attributes.put(property.getName().toString(), property.getValue());
307 }
308 properties = getPropertyNames(fileName);
309 for (DavProperty<?> property : properties) {
310 if (!attributes.containsKey(property.getName().getName())) {
311 property = getProperty(fileName, property.getName());
312 if (property != null) {
313 final Object name = property.getName();
314 final Object value = property.getValue();
315 if (name != null && value != null) {
316 attributes.put(name.toString(), value);
317 }
318 }
319 }
320 }
321 return attributes;
322 } catch (final Exception e) {
323 throw new FileSystemException("vfs.provider.webdav/get-attributes.error", getName(), e);
324 }
325 }
326
327
328
329
330 @Override
331 protected long doGetContentSize() throws Exception {
332 final DavProperty<?> property = getProperty((GenericURLFileName) getName(), DavConstants.PROPERTY_GETCONTENTLENGTH);
333 if (property != null) {
334 final String value = (String) property.getValue();
335 return Long.parseLong(value);
336 }
337 return 0;
338 }
339
340
341
342
343
344 @Override
345 protected long doGetLastModifiedTime() throws Exception {
346 final DavProperty<?> property = getProperty((GenericURLFileName) getName(), DavConstants.PROPERTY_GETLASTMODIFIED);
347 if (property != null) {
348 final String value = (String) property.getValue();
349 return DateUtils.parseDate(value).getTime();
350 }
351 return 0;
352 }
353
354 @Override
355 protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
356 return new WebdavOutputStream(this);
357 }
358
359
360
361
362
363 @Override
364 protected FileType doGetType() throws Exception {
365 try {
366 return isDirectory((GenericURLFileName) getName()) ? FileType.FOLDER : FileType.FILE;
367 } catch (final FileNotFolderException | FileNotFoundException fnfe) {
368 return FileType.IMAGINARY;
369 }
370
371 }
372
373
374
375
376
377
378
379
380
381
382 @Override
383 protected boolean doIsWriteable() throws Exception {
384 return true;
385 }
386
387
388
389
390 @Override
391 protected String[] doListChildren() throws Exception {
392
393 return null;
394 }
395
396
397
398
399 @Override
400 protected FileObject[] doListChildrenResolved() throws Exception {
401 HttpPropfind request = null;
402 try {
403 final GenericURLFileName name = (GenericURLFileName) getName();
404 if (isDirectory(name)) {
405 final DavPropertyNameSet nameSet = new DavPropertyNameSet();
406 nameSet.add(DavPropertyName.create(DavConstants.PROPERTY_DISPLAYNAME));
407
408 request = new HttpPropfind(toUrlString(name), nameSet, DavConstants.DEPTH_1);
409
410 try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
411 final List<Webdav4FileObject> vfs = new ArrayList<>();
412 if (request.succeeded(res)) {
413 final MultiStatusResponse[] responses = request.getResponseBodyAsMultiStatus(res).getResponses();
414
415 for (final MultiStatusResponse response : responses) {
416 if (isCurrentFile(response.getHref(), name)) {
417 continue;
418 }
419 final String resourceName = resourceName(response.getHref());
420 if (!resourceName.isEmpty()) {
421 final Webdav4FileObject fo = (Webdav4FileObject) FileObjectUtils.getAbstractFileObject(
422 getFileSystem().resolveFile(getFileSystem().getFileSystemManager()
423 .resolveName(getName(), resourceName, NameScope.CHILD)));
424 vfs.add(fo);
425 }
426 }
427 }
428 return vfs.toArray(EMPTY_ARRAY);
429 }
430 }
431 throw new FileNotFolderException(getName());
432 } catch (final FileNotFolderException fnfe) {
433 throw fnfe;
434 } catch (final DavException | IOException e) {
435 throw new FileSystemException(e.getMessage(), e);
436 }
437 }
438
439
440
441
442 @Override
443 protected void doRename(final FileObject newFile) throws Exception {
444 final String url = toUrlString((GenericURLFileName) getName());
445 final String dest = toUrlString((GenericURLFileName) newFile.getName(), false);
446 final HttpMove request = setupRequest(new HttpMove(url, dest, false));
447
448 try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
449
450 request.succeeded(res);
451 }
452 }
453
454
455
456
457 @Override
458 protected void doSetAttribute(final String attrName, final Object value) throws Exception {
459 try {
460 final GenericURLFileName fileName = (GenericURLFileName) getName();
461 final String urlStr = toUrlString(fileName);
462 final DavPropertySet properties = new DavPropertySet();
463 final DavPropertyNameSet propertyNameSet = new DavPropertyNameSet();
464 final DavProperty<Object> property = new DefaultDavProperty<>(attrName, value, Namespace.EMPTY_NAMESPACE);
465 if (value != null) {
466 properties.add(property);
467 } else {
468 propertyNameSet.add(property.getName());
469 }
470
471 final HttpProppatch request = setupRequest(new HttpProppatch(urlStr, properties, propertyNameSet));
472
473 try (CloseableHttpResponse response = (CloseableHttpResponse) executeRequest(request)) {
474 if (!request.succeeded(response)) {
475 throw new FileSystemException("Property '" + attrName + "' could not be set.");
476 }
477 }
478 } catch (final FileSystemException fse) {
479 throw fse;
480 } catch (final Exception e) {
481 throw new FileSystemException("vfs.provider.webdav/set-attributes", e, getName(), attrName);
482 }
483 }
484
485 private HttpResponse executeRequest(final HttpUriRequest request) throws FileSystemException {
486 final HttpResponse response;
487
488 try {
489 response = executeHttpUriRequest(request);
490 final int status = response.getStatusLine().getStatusCode();
491 if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) {
492 EntityUtils.consume(response.getEntity());
493 throw new FileNotFoundException(request.getURI());
494 }
495
496 if (request instanceof BaseDavRequest) {
497 ((BaseDavRequest) request).checkSuccess(response);
498 }
499
500 return response;
501 } catch (final FileSystemException fse) {
502 throw fse;
503 } catch (final IOException e) {
504 throw new FileSystemException(e);
505 } catch (final DavException e) {
506 throw ExceptionConverter.generate(e);
507 }
508 }
509
510 @Override
511 protected FileContentInfoFactory getFileContentInfoFactory() {
512 return new Webdav4FileContentInfoFactory();
513 }
514
515 DavPropertySet getProperties(final GenericURLFileName name) throws FileSystemException {
516 return getProperties(name, DavConstants.PROPFIND_ALL_PROP, new DavPropertyNameSet(), false);
517 }
518
519 DavPropertySet getProperties(final GenericURLFileName name, final DavPropertyNameSet nameSet, final boolean addEncoding)
520 throws FileSystemException {
521 return getProperties(name, DavConstants.PROPFIND_BY_PROPERTY, nameSet, addEncoding);
522 }
523
524 DavPropertySet getProperties(final GenericURLFileName name, final int type, final DavPropertyNameSet nameSet,
525 final boolean addEncoding) throws FileSystemException {
526 try {
527 final String urlStr = toUrlString(name);
528 final HttpPropfind request = setupRequest(new HttpPropfind(urlStr, type, nameSet, DavConstants.DEPTH_0));
529 try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
530 if (request.succeeded(res)) {
531 final MultiStatus multiStatus = request.getResponseBodyAsMultiStatus(res);
532 final MultiStatusResponse response = multiStatus.getResponses()[0];
533 final DavPropertySet props = response.getProperties(HttpStatus.SC_OK);
534 if (addEncoding) {
535 final ContentType resContentType = ContentType.getOrDefault(res.getEntity());
536 final DavProperty<String> prop = new DefaultDavProperty<>(RESPONSE_CHARSET,
537 resContentType.getCharset().name());
538 props.add(prop);
539 }
540 return props;
541 }
542 return new DavPropertySet();
543 }
544 } catch (final FileSystemException fse) {
545 throw fse;
546 } catch (final Exception e) {
547 throw new FileSystemException("vfs.provider.webdav/get-property.error", e, getName(), name, type,
548 nameSet.getContent(), addEncoding);
549 }
550 }
551
552 DavProperty<?> getProperty(final GenericURLFileName fileName, final DavPropertyName name) throws FileSystemException {
553 final DavPropertyNameSet nameSet = new DavPropertyNameSet();
554 nameSet.add(name);
555 final DavPropertySet propertySet = getProperties(fileName, nameSet, false);
556 return propertySet.get(name);
557 }
558
559 DavProperty<?> getProperty(final GenericURLFileName fileName, final String property) throws FileSystemException {
560 return getProperty(fileName, DavPropertyName.create(property));
561 }
562
563 DavPropertySet getPropertyNames(final GenericURLFileName name) throws FileSystemException {
564 return getProperties(name, DavConstants.PROPFIND_PROPERTY_NAMES, new DavPropertyNameSet(), false);
565 }
566
567
568
569
570
571
572
573 private String hrefString(final GenericURLFileName name) {
574 try {
575 final GenericURLFileName newFile = new GenericURLFileName(getInternalURI().getScheme(), name.getHostName(), name.getPort(), name.getDefaultPort(),
576 null, null, name.getPath(), name.getType(), name.getQueryString());
577 return newFile.getURIEncoded(getUrlCharset());
578 } catch (final Exception e) {
579 return name.getURI();
580 }
581 }
582
583 private boolean isCurrentFile(final String href, final GenericURLFileName fileName) {
584 String name = hrefString(fileName);
585 if (href.endsWith("/") && !name.endsWith("/")) {
586 name += "/";
587 }
588 return href.equals(name) || href.equals(fileName.getPath());
589 }
590
591 private boolean isDirectory(final GenericURLFileName name) throws IOException {
592 try {
593 final DavProperty<?> property = getProperty(name, DavConstants.PROPERTY_RESOURCETYPE);
594 final Node node;
595 if (property != null && (node = (Node) property.getValue()) != null) {
596 return node.getLocalName().equals(DavConstants.XML_COLLECTION);
597 }
598 return false;
599 } catch (final FileNotFoundException fse) {
600 throw new FileNotFolderException(name);
601 }
602 }
603
604 void log(final Exception ex) {
605
606 }
607
608
609
610
611
612
613
614 private String resourceName(String path) {
615 if (path.endsWith("/")) {
616 path = path.substring(0, path.length() - 1);
617 }
618 final int i = path.lastIndexOf("/");
619 return i >= 0 ? path.substring(i + 1) : path;
620 }
621
622 private <T extends HttpUriRequest> T setupRequest(final T request) {
623
624 request.addHeader("Cache-control", "no-cache");
625 request.addHeader("Cache-store", "no-store");
626 request.addHeader("Pragma", "no-cache");
627 request.addHeader("Expires", "0");
628 return request;
629 }
630
631
632
633
634
635
636
637 String toUrlString(final GenericURLFileName name) {
638 return toUrlString(name, true);
639 }
640
641
642
643
644
645
646
647
648 private String toUrlString(final GenericURLFileName name, final boolean includeUserInfo) {
649 String user = null;
650 String password = null;
651 if (includeUserInfo) {
652 user = name.getUserName();
653 password = name.getPassword();
654 }
655 try {
656 final GenericURLFileName newFile = new Webdav4FileName(getInternalURI().getScheme(), name.getHostName(), name.getPort(), name.getDefaultPort(),
657 user, password, name.getPath(), name.getType(), name.getQueryString(),
658 builder.getAppendTrailingSlash(getFileSystem().getFileSystemOptions()));
659 return newFile.getURIEncoded(getUrlCharset());
660 } catch (final Exception e) {
661 return name.getURI();
662 }
663 }
664 }