package org.dcache.webdav; import io.milton.http.Auth; import io.milton.http.HttpManager; import io.milton.http.LockInfo; import io.milton.http.LockTimeout; import io.milton.http.LockToken; import io.milton.http.Range; import io.milton.http.Request; import io.milton.http.exceptions.BadRequestException; import io.milton.http.exceptions.ConflictException; import io.milton.http.exceptions.NotAuthorizedException; import io.milton.resource.CollectionResource; import io.milton.resource.DeletableResource; import io.milton.resource.GetableResource; import io.milton.resource.LockingCollectionResource; import io.milton.resource.MakeCollectionableResource; import io.milton.resource.PutableResource; import io.milton.resource.Resource; import org.eclipse.jetty.io.EofException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.URISyntaxException; import java.util.Collections; import java.util.List; import java.util.Map; import diskCacheV111.util.CacheException; import diskCacheV111.util.FileExistsCacheException; import diskCacheV111.util.FileNotFoundCacheException; import diskCacheV111.util.FsPath; import diskCacheV111.util.PermissionDeniedCacheException; import org.dcache.auth.Subjects; import org.dcache.vehicles.FileAttributes; /** * Exposes dCache directories as resources in the Milton WebDAV * framework. */ public class DcacheDirectoryResource extends DcacheResource implements PutableResource, GetableResource, DeletableResource, MakeCollectionableResource, LockingCollectionResource { public DcacheDirectoryResource(DcacheResourceFactory factory, FsPath path, FileAttributes attributes) { super(factory, path, attributes); } @Override public String checkRedirect(Request request) { String url = request.getAbsoluteUrl(); if (request.getMethod() == Request.Method.GET && !url.endsWith("/")) { return url + "/"; } return null; } @Override public Resource child(String childName) { FsPath fchild = _path.child(childName); return _factory.getResource(fchild); } @Override public List<? extends Resource> getChildren() { try { return _factory.list(_path); } catch (FileNotFoundCacheException e) { return Collections.emptyList(); } catch (PermissionDeniedCacheException e) { throw WebDavExceptions.permissionDenied(e.getMessage(), e, this); } catch (CacheException | InterruptedException e) { throw new WebDavException(e.getMessage(), e, this); } } @Override public Resource createNew(String newName, InputStream inputStream, Long length, String contentType) throws IOException, ConflictException, NotAuthorizedException, BadRequestException { try { FsPath path = _path.child(newName); if (_factory.shouldRedirect(HttpManager.request())) { throw new RedirectException(this, _factory.getWriteUrl(path, length)); } else { return _factory.createFile(path, inputStream, length); } } catch (EofException e) { // Milton reacts badly to receiving any IOException and wraps the // IOException in a RuntimeException. Here, we translate this to // a bad request, as the Content-Length didn't match the // transferred entity's size. throw new BadRequestException(this, "Connection closed prematurely, entity smaller than expected."); } catch (PermissionDeniedCacheException e) { throw WebDavExceptions.permissionDenied(this); } catch (FileExistsCacheException e) { throw new ConflictException(this); } catch (CacheException e) { throw new WebDavException(e.getMessage(), e, this); } catch (InterruptedException e) { throw new WebDavException("Transfer was interrupted", e, this); } catch (URISyntaxException e) { throw new WebDavException("Invalid request URI: " + e.getMessage(), e, this); } } @Override public void sendContent(OutputStream out, Range range, Map<String,String> params, String contentType) throws IOException, NotAuthorizedException { try { Writer writer = new OutputStreamWriter(out, "UTF-8"); if (!_factory.deliverClient(_path, writer)) { _factory.list(_path, writer); } writer.flush(); } catch (PermissionDeniedCacheException e) { throw WebDavExceptions.permissionDenied(this); } catch (CacheException | InterruptedException e) { throw new WebDavException(e.getMessage(), e, this); } catch (UnsupportedEncodingException e) { throw new RuntimeException("This should not happen as UTF-8 " + "is a required encoding for JVM", e); } } @Override public Long getMaxAgeSeconds(Auth auth) { return null; } @Override public String getContentType(String accepts) { return "text/html; charset=utf-8"; } @Override public Long getContentLength() { return null; } @Override public void delete() throws NotAuthorizedException, ConflictException, BadRequestException { try { _factory.deleteDirectory(_attributes.getPnfsId(), _path); } catch (PermissionDeniedCacheException e) { throw WebDavExceptions.permissionDenied(this); } catch (CacheException e) { throw new WebDavException(e.getMessage(), e, this); } } @Override public CollectionResource createCollection(String newName) throws NotAuthorizedException, ConflictException { try { return _factory.makeDirectory(_attributes, _path.child(newName)); } catch (PermissionDeniedCacheException e) { throw WebDavExceptions.permissionDenied(this); } catch (CacheException e) { throw new WebDavException(e.getMessage(), e, this); } } @Override public LockToken createAndLock(String name, LockTimeout timeout, LockInfo lockInfo) { /* We do not currently support createAndLock, but as Mac OS X * insists on lock support before it allows writing to a * WebDAV store, we return a lock with zero lifetime. * * We do not currently create the entry, as the normal action * after createAndLock is to perform a PUT which immediately * overwrites the empty site. */ return createNullLock(); } }