| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.vfs2.provider.webdav4; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.net.HttpURLConnection; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.vfs2.FileContentInfoFactory; |
| import org.apache.commons.vfs2.FileNotFolderException; |
| import org.apache.commons.vfs2.FileNotFoundException; |
| import org.apache.commons.vfs2.FileObject; |
| import org.apache.commons.vfs2.FileSystemException; |
| import org.apache.commons.vfs2.FileType; |
| import org.apache.commons.vfs2.NameScope; |
| import org.apache.commons.vfs2.provider.AbstractFileName; |
| import org.apache.commons.vfs2.provider.DefaultFileContent; |
| import org.apache.commons.vfs2.provider.GenericURLFileName; |
| import org.apache.commons.vfs2.provider.http4.Http4FileObject; |
| import org.apache.commons.vfs2.util.FileObjectUtils; |
| import org.apache.commons.vfs2.util.MonitorOutputStream; |
| import org.apache.commons.vfs2.util.URIUtils; |
| import org.apache.http.HttpEntity; |
| import org.apache.http.HttpResponse; |
| import org.apache.http.HttpStatus; |
| import org.apache.http.client.methods.HttpPut; |
| import org.apache.http.client.methods.HttpRequestBase; |
| import org.apache.http.client.methods.HttpUriRequest; |
| import org.apache.http.client.utils.DateUtils; |
| import org.apache.http.entity.ByteArrayEntity; |
| import org.apache.http.entity.ContentType; |
| import org.apache.jackrabbit.webdav.DavConstants; |
| import org.apache.jackrabbit.webdav.DavException; |
| import org.apache.jackrabbit.webdav.MultiStatus; |
| import org.apache.jackrabbit.webdav.MultiStatusResponse; |
| import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest; |
| import org.apache.jackrabbit.webdav.client.methods.HttpCheckin; |
| import org.apache.jackrabbit.webdav.client.methods.HttpCheckout; |
| import org.apache.jackrabbit.webdav.client.methods.HttpDelete; |
| import org.apache.jackrabbit.webdav.client.methods.HttpMkcol; |
| import org.apache.jackrabbit.webdav.client.methods.HttpMove; |
| import org.apache.jackrabbit.webdav.client.methods.HttpPropfind; |
| import org.apache.jackrabbit.webdav.client.methods.HttpProppatch; |
| import org.apache.jackrabbit.webdav.client.methods.HttpVersionControl; |
| import org.apache.jackrabbit.webdav.property.DavProperty; |
| import org.apache.jackrabbit.webdav.property.DavPropertyIterator; |
| import org.apache.jackrabbit.webdav.property.DavPropertyName; |
| import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; |
| import org.apache.jackrabbit.webdav.property.DavPropertySet; |
| import org.apache.jackrabbit.webdav.property.DefaultDavProperty; |
| import org.apache.jackrabbit.webdav.version.DeltaVConstants; |
| import org.apache.jackrabbit.webdav.version.VersionControlledResource; |
| import org.apache.jackrabbit.webdav.xml.Namespace; |
| import org.w3c.dom.Node; |
| |
| /** |
| * A WebDAV file. |
| * |
| * @since 2.5.0 |
| */ |
| public class Webdav4FileObject extends Http4FileObject<Webdav4FileSystem> { |
| |
| /** |
| * An empty immutable {@code Webdav4FileObject} array. |
| */ |
| private static final Webdav4FileObject[] EMPTY_ARRAY = new Webdav4FileObject[0]; |
| |
| /** |
| * An OutputStream that writes to a Webdav resource. |
| * <p> |
| * TODO - Use piped stream to avoid temporary file. |
| */ |
| private class WebdavOutputStream extends MonitorOutputStream { |
| private final Webdav4FileObject file; |
| |
| public WebdavOutputStream(final Webdav4FileObject file) { |
| super(new ByteArrayOutputStream()); |
| this.file = file; |
| } |
| |
| private boolean createVersion(final String urlStr) { |
| try { |
| final HttpVersionControl request = new HttpVersionControl(urlStr); |
| setupRequest(request); |
| executeRequest(request); |
| return true; |
| } catch (final Exception ex) { |
| return false; |
| } |
| } |
| |
| /** |
| * Called after this stream is closed. |
| */ |
| @Override |
| protected void onClose() throws IOException { |
| final HttpEntity entity = new ByteArrayEntity(((ByteArrayOutputStream) out).toByteArray()); |
| final GenericURLFileName fileName = (GenericURLFileName) getName(); |
| final String urlStr = toUrlString(fileName); |
| if (builder.isVersioning(getFileSystem().getFileSystemOptions())) { |
| DavPropertySet set = null; |
| boolean fileExists = true; |
| boolean isCheckedIn = true; |
| try { |
| set = getPropertyNames(fileName); |
| } catch (final FileNotFoundException fnfe) { |
| fileExists = false; |
| } |
| if (fileExists && set != null) { |
| if (set.contains(VersionControlledResource.CHECKED_OUT)) { |
| isCheckedIn = false; |
| } else if (!set.contains(VersionControlledResource.CHECKED_IN)) { |
| DavProperty<?> prop = set.get(VersionControlledResource.AUTO_VERSION); |
| if (prop != null) { |
| prop = getProperty(fileName, VersionControlledResource.AUTO_VERSION); |
| if (DeltaVConstants.XML_CHECKOUT_CHECKIN.equals(prop.getValue())) { |
| createVersion(urlStr); |
| } |
| } |
| } |
| } |
| if (fileExists && isCheckedIn) { |
| try { |
| final HttpCheckout request = new HttpCheckout(urlStr); |
| setupRequest(request); |
| executeRequest(request); |
| isCheckedIn = false; |
| } catch (final FileSystemException ex) { |
| log(ex); |
| } |
| } |
| |
| try { |
| final HttpPut request = new HttpPut(urlStr); |
| request.setEntity(entity); |
| setupRequest(request); |
| executeRequest(request); |
| setUserName(fileName, urlStr); |
| } catch (final FileSystemException ex) { |
| if (!isCheckedIn) { |
| try { |
| final HttpCheckin request = new HttpCheckin(urlStr); |
| setupRequest(request); |
| executeRequest(request); |
| isCheckedIn = true; |
| } catch (final Exception e) { |
| // Going to throw original. |
| log(e); |
| } |
| throw ex; |
| } |
| } |
| if (!fileExists) { |
| createVersion(urlStr); |
| try { |
| final DavPropertySet props = getPropertyNames(fileName); |
| isCheckedIn = !props.contains(VersionControlledResource.CHECKED_OUT); |
| } catch (final FileNotFoundException fnfe) { |
| log(fnfe); |
| } |
| } |
| if (!isCheckedIn) { |
| final HttpCheckin request = new HttpCheckin(urlStr); |
| setupRequest(request); |
| executeRequest(request); |
| } |
| } else { |
| final HttpPut request = new HttpPut(urlStr); |
| request.setEntity(entity); |
| setupRequest(request); |
| executeRequest(request); |
| try { |
| setUserName(fileName, urlStr); |
| } catch (final IOException e) { |
| // Unable to set the user name. |
| log(e); |
| } |
| } |
| ((DefaultFileContent) this.file.getContent()).resetAttributes(); |
| } |
| |
| private void setUserName(final GenericURLFileName fileName, final String urlStr) throws IOException { |
| final DavPropertySet setProperties = new DavPropertySet(); |
| final DavPropertyNameSet removeProperties = new DavPropertyNameSet(); |
| String name = builder.getCreatorName(getFileSystem().getFileSystemOptions()); |
| final String userName = fileName.getUserName(); |
| if (name == null) { |
| name = userName; |
| } else if (userName != null) { |
| final String comment = "Modified by user " + userName; |
| setProperties.add(new DefaultDavProperty<>(DeltaVConstants.COMMENT, comment)); |
| } |
| setProperties.add(new DefaultDavProperty<>(DeltaVConstants.CREATOR_DISPLAYNAME, name)); |
| final HttpProppatch request = new HttpProppatch(urlStr, setProperties, removeProperties); |
| setupRequest(request); |
| executeRequest(request); |
| } |
| } |
| |
| /** The character set property name. */ |
| public static final DavPropertyName RESPONSE_CHARSET = DavPropertyName.create("response-charset"); |
| |
| /** The FileSystemConfigBuilder */ |
| private final Webdav4FileSystemConfigBuilder builder; |
| |
| |
| protected Webdav4FileObject(final AbstractFileName name, final Webdav4FileSystem fileSystem) |
| throws FileSystemException { |
| this(name, fileSystem, Webdav4FileSystemConfigBuilder.getInstance()); |
| } |
| |
| void log(Exception ex) { |
| // TODO Consider logging |
| } |
| |
| protected Webdav4FileObject(final AbstractFileName name, final Webdav4FileSystem fileSystem, |
| final Webdav4FileSystemConfigBuilder builder) throws FileSystemException { |
| super(name, fileSystem, builder); |
| this.builder = builder; |
| } |
| |
| /** |
| * Creates this file as a folder. |
| */ |
| @Override |
| protected void doCreateFolder() throws Exception { |
| final HttpMkcol request = new HttpMkcol(toUrlString((GenericURLFileName) getName())); |
| setupRequest(request); |
| try { |
| executeRequest(request); |
| } catch (final FileSystemException fse) { |
| throw new FileSystemException("vfs.provider.webdav/create-collection.error", getName(), fse); |
| } |
| } |
| |
| /** |
| * Deletes the file. |
| */ |
| @Override |
| protected void doDelete() throws Exception { |
| final HttpDelete request = new HttpDelete(toUrlString((GenericURLFileName) getName())); |
| setupRequest(request); |
| executeRequest(request); |
| } |
| |
| /** |
| * Returns the properties of the Webdav resource. |
| */ |
| @Override |
| protected Map<String, Object> doGetAttributes() throws Exception { |
| final Map<String, Object> attributes = new HashMap<>(); |
| try { |
| final GenericURLFileName fileName = (GenericURLFileName) getName(); |
| DavPropertySet properties = getProperties(fileName, DavConstants.PROPFIND_ALL_PROP, |
| new DavPropertyNameSet(), false); |
| final DavPropertyIterator iter = properties.iterator(); |
| while (iter.hasNext()) { |
| final DavProperty<?> property = iter.nextProperty(); |
| attributes.put(property.getName().toString(), property.getValue()); |
| } |
| properties = getPropertyNames(fileName); |
| final DavPropertyIterator iter2 = properties.iterator(); |
| while (iter2.hasNext()) { |
| DavProperty<?> property = iter2.nextProperty(); |
| if (!attributes.containsKey(property.getName().getName())) { |
| property = getProperty(fileName, property.getName()); |
| if (property != null) { |
| final Object name = property.getName(); |
| final Object value = property.getValue(); |
| if (name != null && value != null) { |
| attributes.put(name.toString(), value); |
| } |
| } |
| } |
| } |
| return attributes; |
| } catch (final Exception e) { |
| throw new FileSystemException("vfs.provider.webdav/get-attributes.error", getName(), e); |
| } |
| } |
| |
| /** |
| * Returns the size of the file content (in bytes). |
| */ |
| @Override |
| protected long doGetContentSize() throws Exception { |
| final DavProperty<?> property = getProperty((GenericURLFileName) getName(), DavConstants.PROPERTY_GETCONTENTLENGTH); |
| if (property != null) { |
| final String value = (String) property.getValue(); |
| return Long.parseLong(value); |
| } |
| return 0; |
| } |
| |
| /** |
| * Returns the last modified time of this file. Is only called if {@link #doGetType} does not return |
| * {@link FileType#IMAGINARY}. |
| */ |
| @Override |
| protected long doGetLastModifiedTime() throws Exception { |
| final DavProperty<?> property = getProperty((GenericURLFileName) getName(), DavConstants.PROPERTY_GETLASTMODIFIED); |
| if (property != null) { |
| final String value = (String) property.getValue(); |
| return DateUtils.parseDate(value).getTime(); |
| } |
| return 0; |
| } |
| |
| @Override |
| protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception { |
| return new WebdavOutputStream(this); |
| } |
| |
| /** |
| * Determines the type of this file. Must not return null. The return value of this method is cached, so the |
| * implementation can be expensive. |
| */ |
| @Override |
| protected FileType doGetType() throws Exception { |
| try { |
| return isDirectory((GenericURLFileName) getName()) ? FileType.FOLDER : FileType.FILE; |
| } catch (final FileNotFolderException | FileNotFoundException fnfe) { |
| return FileType.IMAGINARY; |
| } |
| |
| } |
| |
| /** |
| * Determines if this file can be written to. Is only called if {@link #doGetType} does not return |
| * {@link FileType#IMAGINARY}. |
| * <p> |
| * This implementation always returns true. |
| * |
| * @return true if the file is writable. |
| * @throws Exception if an error occurs. |
| */ |
| @Override |
| protected boolean doIsWriteable() throws Exception { |
| return true; |
| } |
| |
| /** |
| * Lists the children of the file. |
| */ |
| @Override |
| protected String[] doListChildren() throws Exception { |
| // use doListChildrenResolved for performance |
| return null; |
| } |
| |
| /** |
| * Lists the children of the file. |
| */ |
| @Override |
| protected FileObject[] doListChildrenResolved() throws Exception { |
| HttpPropfind request = null; |
| try { |
| final GenericURLFileName name = (GenericURLFileName) getName(); |
| if (isDirectory(name)) { |
| final DavPropertyNameSet nameSet = new DavPropertyNameSet(); |
| nameSet.add(DavPropertyName.create(DavConstants.PROPERTY_DISPLAYNAME)); |
| |
| request = new HttpPropfind(toUrlString(name), nameSet, DavConstants.DEPTH_1); |
| |
| final HttpResponse res = executeRequest(request); |
| final List<Webdav4FileObject> vfs = new ArrayList<>(); |
| if (request.succeeded(res)) { |
| final MultiStatusResponse[] responses = request.getResponseBodyAsMultiStatus(res).getResponses(); |
| |
| for (final MultiStatusResponse response : responses) { |
| if (isCurrentFile(response.getHref(), name)) { |
| continue; |
| } |
| final String resourceName = resourceName(response.getHref()); |
| if (resourceName != null && !resourceName.isEmpty()) { |
| final Webdav4FileObject fo = (Webdav4FileObject) FileObjectUtils.getAbstractFileObject( |
| getFileSystem().resolveFile(getFileSystem().getFileSystemManager() |
| .resolveName(getName(), resourceName, NameScope.CHILD))); |
| vfs.add(fo); |
| } |
| } |
| } |
| return vfs.toArray(EMPTY_ARRAY); |
| } |
| throw new FileNotFolderException(getName()); |
| } catch (final FileNotFolderException fnfe) { |
| throw fnfe; |
| } catch (final DavException | IOException e) { |
| throw new FileSystemException(e.getMessage(), e); |
| } finally { |
| if (request != null) { |
| request.releaseConnection(); |
| } |
| } |
| } |
| |
| /** |
| * Rename the file. |
| */ |
| @Override |
| protected void doRename(final FileObject newFile) throws Exception { |
| final String url = URIUtils.encodePath(toUrlString((GenericURLFileName) getName())); |
| final String dest = toUrlString((GenericURLFileName) newFile.getName(), false); |
| final HttpMove request = new HttpMove(url, dest, false); |
| setupRequest(request); |
| executeRequest(request); |
| } |
| |
| /** |
| * Sets an attribute of this file. Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. |
| */ |
| @Override |
| protected void doSetAttribute(final String attrName, final Object value) throws Exception { |
| try { |
| final GenericURLFileName fileName = (GenericURLFileName) getName(); |
| final String urlStr = toUrlString(fileName); |
| final DavPropertySet properties = new DavPropertySet(); |
| final DavPropertyNameSet propertyNameSet = new DavPropertyNameSet(); |
| final DavProperty<Object> property = new DefaultDavProperty<>(attrName, value, Namespace.EMPTY_NAMESPACE); |
| if (value != null) { |
| properties.add(property); |
| } else { |
| propertyNameSet.add(property.getName()); // remove property |
| } |
| |
| final HttpProppatch request = new HttpProppatch(urlStr, properties, propertyNameSet); |
| setupRequest(request); |
| final HttpResponse response = executeRequest(request); |
| if (!request.succeeded(response)) { |
| throw new FileSystemException("Property '" + attrName + "' could not be set."); |
| } |
| } catch (final FileSystemException fse) { |
| throw fse; |
| } catch (final Exception e) { |
| throw new FileSystemException("vfs.provider.webdav/set-attributes", e, getName(), attrName); |
| } |
| } |
| |
| private HttpResponse executeRequest(final HttpUriRequest request) throws FileSystemException { |
| final HttpResponse response; |
| |
| try { |
| response = executeHttpUriRequest(request); |
| final int status = response.getStatusLine().getStatusCode(); |
| if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) { |
| throw new FileNotFoundException(request.getURI()); |
| } |
| |
| if (request instanceof BaseDavRequest) { |
| ((BaseDavRequest) request).checkSuccess(response); |
| } |
| |
| return response; |
| } catch (final FileSystemException fse) { |
| throw fse; |
| } catch (final IOException e) { |
| throw new FileSystemException(e); |
| } catch (final DavException e) { |
| throw ExceptionConverter.generate(e); |
| } finally { |
| if (request instanceof HttpRequestBase) { |
| ((HttpRequestBase) request).releaseConnection(); |
| } |
| } |
| } |
| |
| @Override |
| protected FileContentInfoFactory getFileContentInfoFactory() { |
| return new Webdav4FileContentInfoFactory(); |
| } |
| |
| DavPropertySet getProperties(final GenericURLFileName name) throws FileSystemException { |
| return getProperties(name, DavConstants.PROPFIND_ALL_PROP, new DavPropertyNameSet(), false); |
| } |
| |
| DavPropertySet getProperties(final GenericURLFileName name, final DavPropertyNameSet nameSet, final boolean addEncoding) |
| throws FileSystemException { |
| return getProperties(name, DavConstants.PROPFIND_BY_PROPERTY, nameSet, addEncoding); |
| } |
| |
| DavPropertySet getProperties(final GenericURLFileName name, final int type, final DavPropertyNameSet nameSet, |
| final boolean addEncoding) throws FileSystemException { |
| try { |
| final String urlStr = toUrlString(name); |
| final HttpPropfind request = new HttpPropfind(urlStr, type, nameSet, DavConstants.DEPTH_0); |
| setupRequest(request); |
| final HttpResponse res = executeRequest(request); |
| if (request.succeeded(res)) { |
| final MultiStatus multiStatus = request.getResponseBodyAsMultiStatus(res); |
| final MultiStatusResponse response = multiStatus.getResponses()[0]; |
| final DavPropertySet props = response.getProperties(HttpStatus.SC_OK); |
| if (addEncoding) { |
| final ContentType resContentType = ContentType.getOrDefault(res.getEntity()); |
| final DavProperty<String> prop = new DefaultDavProperty<>(RESPONSE_CHARSET, |
| resContentType.getCharset().name()); |
| props.add(prop); |
| } |
| return props; |
| } |
| return new DavPropertySet(); |
| } catch (final FileSystemException fse) { |
| throw fse; |
| } catch (final Exception e) { |
| throw new FileSystemException("vfs.provider.webdav/get-property.error", e, getName(), name, type, |
| nameSet.getContent(), addEncoding); |
| } |
| } |
| |
| DavProperty<?> getProperty(final GenericURLFileName fileName, final DavPropertyName name) throws FileSystemException { |
| final DavPropertyNameSet nameSet = new DavPropertyNameSet(); |
| nameSet.add(name); |
| final DavPropertySet propertySet = getProperties(fileName, nameSet, false); |
| return propertySet.get(name); |
| } |
| |
| DavProperty<?> getProperty(final GenericURLFileName fileName, final String property) throws FileSystemException { |
| return getProperty(fileName, DavPropertyName.create(property)); |
| } |
| |
| DavPropertySet getPropertyNames(final GenericURLFileName name) throws FileSystemException { |
| return getProperties(name, DavConstants.PROPFIND_PROPERTY_NAMES, new DavPropertyNameSet(), false); |
| } |
| |
| /** |
| * Convert the FileName to an encoded url String. |
| * |
| * @param name The FileName. |
| * @return The encoded URL String. |
| */ |
| private String hrefString(final GenericURLFileName name) { |
| try { |
| final GenericURLFileName newFile = new GenericURLFileName(getInternalURI().getScheme(), name.getHostName(), name.getPort(), name.getDefaultPort(), |
| null, null, name.getPath(), name.getType(), name.getQueryString()); |
| return newFile.getURIEncoded(this.getUrlCharset()); |
| } catch (final Exception e) { |
| return name.getURI(); |
| } |
| } |
| |
| private boolean isCurrentFile(final String href, final GenericURLFileName fileName) { |
| String name = hrefString(fileName); |
| if (href.endsWith("/") && !name.endsWith("/")) { |
| name += "/"; |
| } |
| return href.equals(name) || href.equals(fileName.getPath()); |
| } |
| |
| private boolean isDirectory(final GenericURLFileName name) throws IOException { |
| try { |
| final DavProperty<?> property = getProperty(name, DavConstants.PROPERTY_RESOURCETYPE); |
| final Node node; |
| if (property != null && (node = (Node) property.getValue()) != null) { |
| return node.getLocalName().equals(DavConstants.XML_COLLECTION); |
| } |
| return false; |
| } catch (final FileNotFoundException fse) { |
| throw new FileNotFolderException(name); |
| } |
| } |
| |
| /** |
| * Returns the resource name from the path. |
| * |
| * @param path the path to the file. |
| * @return The resource name |
| */ |
| private String resourceName(String path) { |
| if (path.endsWith("/")) { |
| path = path.substring(0, path.length() - 1); |
| } |
| final int i = path.lastIndexOf("/"); |
| return i >= 0 ? path.substring(i + 1) : path; |
| } |
| |
| private void setupRequest(final HttpUriRequest request) { |
| // NOTE: *FileSystemConfigBuilder takes care of redirect option and user agent. |
| request.addHeader("Cache-control", "no-cache"); |
| request.addHeader("Cache-store", "no-store"); |
| request.addHeader("Pragma", "no-cache"); |
| request.addHeader("Expires", "0"); |
| } |
| |
| /** |
| * Converts the given URLFileName to an encoded URL String to internally use in real DAV operations. |
| * |
| * @param name The FileName. |
| * @return The encoded URL String. |
| */ |
| String toUrlString(final GenericURLFileName name) { |
| return toUrlString(name, true); |
| } |
| |
| /** |
| * Converts the given URLFileName to an encoded URL String to internally use in real DAV operations. |
| * |
| * @param name The FileName. |
| * @param includeUserInfo true if user information should be included. |
| * @return The encoded URL String. |
| */ |
| private String toUrlString(final GenericURLFileName name, final boolean includeUserInfo) { |
| String user = null; |
| String password = null; |
| if (includeUserInfo) { |
| user = name.getUserName(); |
| password = name.getPassword(); |
| } |
| try { |
| final GenericURLFileName newFile = new GenericURLFileName(getInternalURI().getScheme(), name.getHostName(), name.getPort(), name.getDefaultPort(), |
| user, password, name.getPath(), name.getType(), name.getQueryString()); |
| return newFile.getURIEncoded(this.getUrlCharset()); |
| } catch (final Exception e) { |
| return name.getURI(); |
| } |
| } |
| } |