diff --git a/api/src/org/labkey/api/data/TempTableTracker.java b/api/src/org/labkey/api/data/TempTableTracker.java index 00498dbd74a..7833ac611aa 100644 --- a/api/src/org/labkey/api/data/TempTableTracker.java +++ b/api/src/org/labkey/api/data/TempTableTracker.java @@ -163,11 +163,13 @@ private void untrack() synchronized(createdTableNames) { - createdTableNames.remove(qualifiedName); + var ttt = createdTableNames.remove(qualifiedName); appendToLog("-" + schemaName + "\t" + tableName + "\n"); if (createdTableNames.isEmpty() || System.currentTimeMillis() > lastSync + CacheManager.DAY) synchronizeLog(false); + + ttt.clear(); } } diff --git a/api/src/org/labkey/api/util/FileUtil.java b/api/src/org/labkey/api/util/FileUtil.java index 2de839009dc..917d8d41940 100644 --- a/api/src/org/labkey/api/util/FileUtil.java +++ b/api/src/org/labkey/api/util/FileUtil.java @@ -388,6 +388,22 @@ public static boolean mkdirs(File file, boolean checkFileName) throws IOExceptio return file.mkdirs(); } + public static boolean mkdirs(FileLike file, boolean checkFileName) throws IOException + { + FileLike parent = file; + var ret = false; + while (!Files.exists(parent.toNioPathForWrite())) + { + ret = true; + if (checkFileName) + checkAllowedFileName(parent.getName()); + parent = parent.getParent(); + } + //noinspection SSBasedInspection + file.mkdirs(); + return ret; + } + public static Path createDirectory(Path path) throws IOException { @@ -435,6 +451,13 @@ public static Path createDirectories(Path path, boolean checkFileName) throws IO } + public static boolean renameTo(FileLike from, FileLike to) + { + // TODO FileLike.renameTo() + return toFileForRead(from).renameTo(toFileForWrite(to)); + } + + public static boolean createNewFile(File file) throws IOException { return createNewFile(file, AppProps.getInstance().isInvalidFilenameBlocked()); @@ -450,6 +473,16 @@ public static boolean createNewFile(File file, boolean checkFileName) throws IOE } + public static boolean createNewFile(FileLike file, boolean checkFileName) throws IOException + { + if (checkFileName) + checkAllowedFileName(file.getName()); + var ret = !file.exists(); + file.createFile(); + return ret; + } + + public static Path createFile(Path path, FileAttribute... attrs) throws IOException { return createFile(path, AppProps.getInstance().isInvalidFilenameBlocked(), attrs); @@ -591,6 +624,12 @@ public static boolean hasCloudScheme(String url) } + public static boolean hasCloudScheme(FileLike filelike) + { + return "s3".equals(filelike.getFileSystem().getScheme()); + } + + public static String getAbsolutePath(Path path) { if (!FileUtil.hasCloudScheme(path)) diff --git a/api/src/org/labkey/api/util/Path.java b/api/src/org/labkey/api/util/Path.java index fb648bbd884..766f15114fb 100644 --- a/api/src/org/labkey/api/util/Path.java +++ b/api/src/org/labkey/api/util/Path.java @@ -43,6 +43,7 @@ public class Path implements Serializable, Comparable, Iterable { + final private boolean _caseSensitive; final private int _hash; final private String[] _path; final private int _length; @@ -58,17 +59,20 @@ public class Path implements Serializable, Comparable, Iterable transient private AtomicReference _parent; - final public static Path emptyPath = new Path(new String[0], 0, false, true); - final public static Path rootPath = new Path(new String[0], 0, true, true); + final public static Path emptyPath = new Path(new String[0], 0, false, true, false); + final public static Path rootPath = new Path(new String[0], 0, true, true, false); @JsonCreator - private Path(@JsonProperty("_hash") int hash, + private Path( + @JsonProperty("_hash") int hash, @JsonProperty("_path") String[] path, @JsonProperty("_length") int length, @JsonProperty("_isAbsolute") boolean abs, - @JsonProperty("_isDirectory") boolean dir) + @JsonProperty("_isDirectory") boolean dir, + @JsonProperty("_caseSensitive") boolean caseSensitive) { + _caseSensitive = caseSensitive; _hash = hash; _path = path; _length = length; @@ -77,7 +81,7 @@ private Path(@JsonProperty("_hash") int hash, _parent = new AtomicReference<>(); } - protected Path(String[] path, int length, boolean abs, boolean dir) + protected Path(String[] path, int length, boolean abs, boolean dir, boolean caseSensitive) { _path = path; _length = length; @@ -85,6 +89,7 @@ protected Path(String[] path, int length, boolean abs, boolean dir) _isAbsolute = abs; _isDirectory = dir; _hash = computeHash(_path, _length); + _caseSensitive = caseSensitive; } // Create an instance from a java.nio.file.Path @@ -93,16 +98,27 @@ public Path(java.nio.file.Path nioPath) this(getNames(nioPath)); } - protected Path(Path anotherPath) + public Path(Path anotherPath) { + this(anotherPath, anotherPath.isAbsolute()); + } + + protected Path(Path anotherPath, boolean caseSensitive) + { + _caseSensitive = caseSensitive; _hash = anotherPath._hash; _path = anotherPath._path; _length = anotherPath._length; _isAbsolute = anotherPath._isAbsolute; - _isDirectory = anotherPath._isAbsolute; + _isDirectory = anotherPath._isDirectory; _parent = anotherPath._parent; } + public boolean isCaseSensitive() + { + return _caseSensitive; + } + private static Collection getNames(Iterable it) { return StreamSupport.stream(it.spliterator(), false) @@ -110,21 +126,19 @@ private static Collection getNames(Iterable it) .collect(Collectors.toList()); } - public Path(Collection names) { this(names.toArray(new String[0])); } - public Path() { - this(new String[0], 0, false, false); + this(new String[0], 0, false, false, false); } public Path(String ... names) { - this(names, names.length, false, false); + this(names, names.length, false, false, false); } public Path(Path.Part ... names) @@ -138,6 +152,7 @@ public Path(Path.Part ... names) _isAbsolute = false; _isDirectory = false; _hash = computeHash(_path, _length); + _caseSensitive = false; } private static int computeHash(String[] names, int length) @@ -157,7 +172,19 @@ public static Path parse(@NotNull String path) String[] arr = strip.split("/"); for (int i=0 ; i=0 ; i--) { - int c = compareName(_path[i], that._path[i]); + int c = compareName(that, _path[i], that._path[i]); if (0 != c) return false; } @@ -323,7 +380,7 @@ public Path getParent() Path p = _parent.get(); if (null != p) return p; - _parent.compareAndSet(null, createPath(_path, _length-1, isAbsolute(), true)); + _parent.compareAndSet(null, createPath(_path, _length-1, isAbsolute(), true, _caseSensitive)); return _parent.get(); } @@ -366,7 +423,7 @@ else if ("..".equals(part)) normal[next++] = part; } } - return createPath(normal, next, isAbsolute(), isDirectory()); + return createPath(normal, next, isAbsolute(), isDirectory(), isCaseSensitive()); } /** @@ -378,9 +435,9 @@ public Path relativize(Path other) return other; int shorter = Math.min(_length, other._length); int i; - for (i=0 ; i file.resolveChild(name.toString())) + .map(file -> file.resolveChild(name)) .toList(); } @@ -259,14 +260,32 @@ public String getAbsolutePath(User user) return null; } + + @Override + public long copyFrom(User user, WebdavResource r) throws IOException, DavException + { + File from = r.getFile(); + File to = getFile(); + if (null != from && null != to) + { + FileUtil.createNewFile(to, AppProps.getInstance().isInvalidFilenameUploadBlocked()); + FileUtil.copyFile(from, to); + to.setLastModified(from.lastModified()); + return to.length(); + } + return super.copyFrom(user, r); + } + + @Override public long copyFrom(User user, FileStream is) throws IOException { - File file = getFile(); + FileLike file = getFileLike(); boolean created = false; if (!file.exists()) { - FileUtil.mkdirs(file.getParentFile(), AppProps.getInstance().isInvalidFilenameUploadBlocked()); + var parent = file.getParent(); + FileUtil.mkdirs(parent, AppProps.getInstance().isInvalidFilenameUploadBlocked()); try { FileUtil.createNewFile(file, AppProps.getInstance().isInvalidFilenameUploadBlocked()); @@ -277,18 +296,26 @@ public long copyFrom(User user, FileStream is) throws IOException _log.error("Couldn't create file on server: " + file.getPath(), x); throw new ConfigurationException("Couldn't create file on server", x); } - resetMetadata(); } try { - is.transferTo(file); - if (is.getLastModified() != null) + File ioFile = getFile(); + if (null != ioFile) + { + is.transferTo(getFile()); + if (is.getLastModified() != null) + ioFile.setLastModified(is.getLastModified().getTime()); + } + else { - file.setLastModified(is.getLastModified().getTime()); + try (var out = file.openOutputStream()) + { + StreamUtils.copy(is.openInputStream(),out); + } } resetMetadata(); - return file.length(); + return file.getSize(); } catch (IOException x) { @@ -300,6 +327,7 @@ public long copyFrom(User user, FileStream is) throws IOException finally { is.closeInputStream(); + resetMetadata(); } } diff --git a/api/src/org/labkey/vfs/AbstractFileLike.java b/api/src/org/labkey/vfs/AbstractFileLike.java index aef8510dc38..e2322112da3 100644 --- a/api/src/org/labkey/vfs/AbstractFileLike.java +++ b/api/src/org/labkey/vfs/AbstractFileLike.java @@ -2,6 +2,7 @@ import org.jetbrains.annotations.NotNull; import org.labkey.api.util.Path; +import org.labkey.api.view.UnauthorizedException; import java.io.IOException; import java.io.InputStream; @@ -20,13 +21,12 @@ protected AbstractFileLike(Path path) throw new IllegalArgumentException("Path must be normalized"); if (!path.isAbsolute()) throw new IllegalArgumentException("Path must be absolute"); - this.path = path; + this.path = getFileSystem().pathOf(path); } @Override final public Path getPath() { - return path; } @@ -63,6 +63,8 @@ public void refresh() @Override public void mkdir() throws IOException { + if (!getFileSystem().canWriteFiles()) + throw new UnauthorizedException(); refresh(); if (exists()) return; @@ -77,6 +79,8 @@ public void mkdir() throws IOException @Override public void mkdirs() throws IOException { + if (!getFileSystem().canWriteFiles()) + throw new UnauthorizedException(); refresh(); if (exists()) return; diff --git a/api/src/org/labkey/vfs/AbstractFileSystemLike.java b/api/src/org/labkey/vfs/AbstractFileSystemLike.java index 68b29764c42..7d36bbad5af 100644 --- a/api/src/org/labkey/vfs/AbstractFileSystemLike.java +++ b/api/src/org/labkey/vfs/AbstractFileSystemLike.java @@ -1,5 +1,7 @@ package org.labkey.vfs; +import org.apache.commons.lang3.StringUtils; + import java.net.URI; import java.nio.file.Path; @@ -7,17 +9,18 @@ abstract public class AbstractFileSystemLike implements FileSystemLike { final URI uri; final String scheme; - final String strUri; // no trailing '/' + final boolean caseSensitive; + final String strUri; // no trailing '/' final boolean canDeleteRoot; final boolean canList = true; final boolean canRead; final boolean canWrite; - AbstractFileSystemLike(URI uri, boolean canRead, boolean canWrite, boolean canDeleteRoot) + protected AbstractFileSystemLike(URI uri, boolean caseSensitive, boolean canRead, boolean canWrite, boolean canDeleteRoot) { - // Is there value in re-encoding the URI so that it is consistently encoded? + this.caseSensitive = caseSensitive; this.uri = uri; - this.strUri = uri.toString(); + this.strUri = StringUtils.appendIfMissing(uri.toString(),"/"); this.scheme = uri.getScheme(); this.canRead = canRead; this.canWrite = canWrite; @@ -36,6 +39,12 @@ public URI getURI() return uri; } + @Override + public URI getURI(FileLike fo) + { + return URI.create(strUri + fo.getPath().encode("",null)); + } + @Override public String getScheme() { @@ -71,4 +80,26 @@ public boolean canWriteFiles() { return canWrite; } + + @Override + public org.labkey.api.util.Path parsePath(String str) + { + var ret = caseSensitive ? + org.labkey.api.util.Path.parseCaseSensitive(str) : + org.labkey.api.util.Path.parse(str); + assert caseSensitive == ret.isCaseSensitive(); + return ret; + } + + @Override + public org.labkey.api.util.Path pathOf(org.labkey.api.util.Path path) + { + if (path.isCaseSensitive() == caseSensitive) // don't really need this, but it makes it easier to set a breakpoint when != + return path; + var ret = caseSensitive ? + path.caseSensitive() : + path.caseInsensitive(); + assert ret.isCaseSensitive() == caseSensitive; + return ret; + } } diff --git a/api/src/org/labkey/vfs/FileLike.java b/api/src/org/labkey/vfs/FileLike.java index b7aa3a1a784..0466976cfd7 100644 --- a/api/src/org/labkey/vfs/FileLike.java +++ b/api/src/org/labkey/vfs/FileLike.java @@ -67,6 +67,11 @@ default java.nio.file.Path toNioPathForWrite() /* We use util.Path here to avoid ambiguity of String (encoded vs not encoded, path vs name, etc). */ FileLike resolveFile(org.labkey.api.util.Path path); + default FileLike resolveChild(Path.Part name) + { + return resolveChild(name.toString()); + } + default FileLike resolveChild(String name) { if (".".equals(name) || "..".equals(name)) @@ -106,6 +111,8 @@ default FileLike resolveChild(String name) long getSize(); + long getCreated(); + long getLastModified(); OutputStream openOutputStream() throws IOException; diff --git a/api/src/org/labkey/vfs/FileSystemLike.java b/api/src/org/labkey/vfs/FileSystemLike.java index c4bb43cc1fd..5fc2219fe38 100644 --- a/api/src/org/labkey/vfs/FileSystemLike.java +++ b/api/src/org/labkey/vfs/FileSystemLike.java @@ -52,6 +52,13 @@ */ public interface FileSystemLike { + // NOTE: a full webdav path consist of case-sensitive and case-sensitive parts + // However, the relative part of the path into a file system will be consistently sensitive or not + // These helpers can be used to make the correct Path for this VFS + + org.labkey.api.util.Path parsePath(String str); + org.labkey.api.util.Path pathOf(org.labkey.api.util.Path path); + /* * Create a file system that return FileLike objects that cache basic file meta-data such as type (file/directory) * and direct children. refresh() can be used to force reload of metadata. @@ -91,8 +98,8 @@ default boolean isDescendant(FileLike base, URI uri) } /** BasicFileAttributes uses more memory than we really need, so this is the basics */ - record MinimalFileAttributes(boolean exists, boolean file, boolean directory, long size, long lastModified) {} - MinimalFileAttributes NULL_ATTRIBUTES = new MinimalFileAttributes(false, false, false, 0, 0); + record MinimalFileAttributes(boolean exists, boolean file, boolean directory, long size, long lastModified, long created) {} + MinimalFileAttributes NULL_ATTRIBUTES = new MinimalFileAttributes(false, false, false, 0, 0, 0); class Builder @@ -191,6 +198,8 @@ static FileLike wrapFile(File f) static FileLike wrapFile(java.nio.file.Path p) { + if (null == p) + return null; return wrapFile(p.toFile()); } @@ -207,6 +216,8 @@ static FileLike wrapFile(File root, File f) throws IOException /** Helper for partially converted code. May throw if the FileLike does not wrap a local file system. */ static File toFile(FileLike f) { + if (null == f) + return null; java.nio.file.Path p = f.getFileSystem().getNioPath(f); return p.toFile(); } @@ -226,6 +237,20 @@ static List wrapFiles(List files) return ret; } + static List wrapPaths(List paths) + { + Map map = new HashMap<>(); + List ret = new ArrayList<>(paths.size()); + for (var path : paths) + { + var file = path.toFile(); + File parent = file.getParentFile(); + FileSystemLike fs = map.computeIfAbsent(parent, key -> new FileSystemLike.Builder(parent).readwrite().build()); + ret.add(fs.resolveFile(new Path(file.getName()))); + } + return ret; + } + static Map wrapFiles(Map files) { Map map = new HashMap<>(); diff --git a/api/src/org/labkey/vfs/FileSystemLocal.java b/api/src/org/labkey/vfs/FileSystemLocal.java index 6eb8cd69f38..9c878f56fe2 100644 --- a/api/src/org/labkey/vfs/FileSystemLocal.java +++ b/api/src/org/labkey/vfs/FileSystemLocal.java @@ -36,9 +36,9 @@ public class FileSystemLocal extends AbstractFileSystemLike private FileSystemLocal(URI uri, boolean caching, boolean canRead, boolean canWrite, boolean canDeleteRoot) { - super(uri, canRead, canWrite, canDeleteRoot); + super(uri, !FileUtil.isCaseInsensitiveFileSystem(), canRead, canWrite, canDeleteRoot); this.nioRoot = java.nio.file.Path.of(uri); - this.root = createFileLike(Path.rootPath, new File(uri)); + this.root = createFileLike(pathOf(Path.rootPath), new File(uri)); this.caching = caching; } @@ -155,8 +155,6 @@ public boolean delete() throws IOException @Override final public void _mkdirs() throws IOException { - if (!canWriteFiles()) - throw new UnauthorizedException(); try { FileUtil.mkdirs(file); @@ -191,6 +189,20 @@ public long getSize() return file.length(); } + @Override + public long getCreated() + { + try + { + var att = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + return att.creationTime().toMillis(); + } + catch (IOException e) + { + return Long.MIN_VALUE; + } + } + @Override public long getLastModified() { @@ -270,7 +282,7 @@ class _CachingFileLike extends _FileLike var att = Files.readAttributes(file.toPath(), BasicFileAttributes.class); synchronized (this) { - attributes = new MinimalFileAttributes(true, att.isRegularFile(), !att.isRegularFile(), att.size(), att.lastModifiedTime().toMillis()); + attributes = new MinimalFileAttributes(true, att.isRegularFile(), !att.isRegularFile(), att.size(), att.lastModifiedTime().toMillis(), att.creationTime().toMillis()); return attributes; } } @@ -322,6 +334,18 @@ public long getSize() return getAttributes().size(); } + @Override + public long getCreated() + { + return getAttributes().created(); + } + + @Override + public long getLastModified() + { + return getAttributes().lastModified(); + } + @Override public @NotNull List getChildren() { diff --git a/api/src/org/labkey/vfs/FileSystemVFS.java b/api/src/org/labkey/vfs/FileSystemVFS.java index eab177bb854..641b4863283 100644 --- a/api/src/org/labkey/vfs/FileSystemVFS.java +++ b/api/src/org/labkey/vfs/FileSystemVFS.java @@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.util.ConfigurationException; +import org.labkey.api.util.FileUtil; import org.labkey.api.util.Path; import org.labkey.api.util.UnexpectedException; import org.labkey.api.view.UnauthorizedException; @@ -35,11 +36,16 @@ public class FileSystemVFS extends AbstractFileSystemLike FileSystemVFS(URI uri, boolean canRead, boolean canWrite, boolean canDeleteRoot) { - super(uri, canRead, canWrite, canDeleteRoot); + this(uri, !FileUtil.isCaseInsensitiveFileSystem(), canRead, canWrite, canDeleteRoot); + } + + FileSystemVFS(URI uri, boolean caseSensitive, boolean canRead, boolean canWrite, boolean canDeleteRoot) + { + super(uri, caseSensitive, canRead, canWrite, canDeleteRoot); try { vfsRoot = VFS.getManager().resolveFile(uri); - root = new _FileLike(Path.rootPath, vfsRoot); + root = new _FileLike(parsePath("/"), vfsRoot); } catch (FileSystemException e) { @@ -47,9 +53,9 @@ public class FileSystemVFS extends AbstractFileSystemLike } } - private FileSystemVFS(URI uri, @Nullable CacheStrategy cacheStrategy, boolean canRead, boolean canWrite, boolean canDeleteRoot) + private FileSystemVFS(URI uri, @Nullable CacheStrategy cacheStrategy, boolean caseSensitive, boolean canRead, boolean canWrite, boolean canDeleteRoot) { - super(uri, canRead, canWrite, canDeleteRoot); + super(uri, caseSensitive, canRead, canWrite, canDeleteRoot); try { var manager = VFS.getManager(); @@ -75,7 +81,7 @@ public FileSystemLike getCachingFileSystem() var cacheStrategy = vfsRoot.getFileSystem().getFileSystemManager().getCacheStrategy(); if (cacheStrategy != CacheStrategy.ON_CALL) return this; - return new FileSystemVFS(getURI(), CacheStrategy.ON_RESOLVE, canReadFiles(), canWriteFiles(), canDeleteRoot()); + return new FileSystemVFS(getURI(), CacheStrategy.ON_RESOLVE, caseSensitive, canReadFiles(), canWriteFiles(), canDeleteRoot()); } @Override @@ -234,6 +240,12 @@ public long getSize() } } + @Override + public long getCreated() + { + return getLastModified(); + } + @Override public long getLastModified() { @@ -243,7 +255,7 @@ public long getLastModified() } catch (FileSystemException e) { - return 0; + return Long.MIN_VALUE; } } diff --git a/core/src/org/labkey/core/webdav/DavController.java b/core/src/org/labkey/core/webdav/DavController.java index b283fdcd100..893388b138d 100644 --- a/core/src/org/labkey/core/webdav/DavController.java +++ b/core/src/org/labkey/core/webdav/DavController.java @@ -827,6 +827,27 @@ WebdavStatus doMethod() throws DavException, IOException, RedirectException getResponse().setStatus(ret); return ret; } + + protected Pair getDepthParameter(int defaultDepth, int maxDepth, boolean defaultListRoot) + { + String depthStr = getRequest().getHeader("Depth"); + if (null == depthStr) + depthStr = getRequest().getParameter("depth"); + if (null == depthStr) + return new Pair<>(defaultDepth, defaultListRoot); + int depth = defaultDepth; + boolean noroot = depthStr.endsWith(",noroot"); + if (noroot) + depthStr = depthStr.substring(0,depthStr.length()-",noroot".length()); + try + { + depth = Math.min(maxDepth, Math.max(0,Integer.parseInt(depthStr.trim()))); + } + catch (NumberFormatException x) + { + } + return new Pair<>(depth, depth>0 && noroot); + } } @@ -1267,7 +1288,7 @@ protected InputStream getInputStream() } @Override - protected Pair getDepthParameter() + protected Pair getDepthParameter(int defaultDepth, int maxDepth, boolean defaultListRoot) { return new Pair<>(0,Boolean.FALSE); } @@ -1346,28 +1367,6 @@ protected InputStream getInputStream() throws IOException return getRequest().getInputStream(); } - protected Pair getDepthParameter() - { - String depthStr = getRequest().getHeader("Depth"); - if (null == depthStr) - depthStr = getRequest().getParameter("depth"); - if (null == depthStr) - return new Pair<>(defaultDepth, defaultListRoot); - int depth = defaultDepth; - boolean noroot = depthStr.endsWith(",noroot"); - if (noroot) - depthStr = depthStr.substring(0,depthStr.length()-",noroot".length()); - try - { - depth = Math.min(INFINITY, Math.max(0,Integer.parseInt(depthStr.trim()))); - } - catch (NumberFormatException x) - { - } - return new Pair<>(depth, depth>0 && noroot); - } - - protected ResourceFilter getResourceFilter() { Boolean isCollection = getBooleanParameter("isCollection"); @@ -1413,7 +1412,7 @@ public WebdavStatus doMethod() throws DavException, IOException List properties = null; Find type = null; - Pair depthParam = getDepthParameter(); + Pair depthParam = getDepthParameter(defaultDepth, maxDepth, defaultListRoot); int depth = depthParam.first; boolean noroot = depthParam.second; @@ -1546,18 +1545,17 @@ else if (Find.FIND_BY_PROPERTY == type && null != propNode) else { // The stack always contains the object of the current level - LinkedList stack = new LinkedList<>(); - stack.addLast(root.getPath()); + LinkedList stack = new LinkedList<>(); + stack.addLast(root); // Stack of the objects one level below boolean skipFirst = noroot; WebdavResource resource; - LinkedList stackBelow = new LinkedList<>(); + LinkedList stackBelow = new LinkedList<>(); while ((!stack.isEmpty()) && (depth >= 0)) { - Path currentPath = stack.removeFirst(); - resource = resolvePath(currentPath); + resource = stack.removeFirst(); if (null == resource || !resource.canList(getUser(), true)) continue; @@ -1572,16 +1570,13 @@ else if (f.accept(resource)) if (resource.isCollection() && depth > 0) { - Collection listPaths = resource.listNames(); - for (String listPath : listPaths) - { - Path newPath = currentPath.append(listPath); - stackBelow.addLast(newPath); - } + var listResources = resource.list(); + if (null != listResources) + stackBelow.addAll(listResources); // Displaying the lock-null resources present in that // collection - List currentLockNullResources = lockNullResources.get(currentPath); + List currentLockNullResources = lockNullResources.get(resource.getPath()); if (currentLockNullResources != null) { for (Path currentLockNullResource : currentLockNullResources) @@ -1748,37 +1743,34 @@ public WebdavStatus doMethod() throws DavException, IOException resourceWriter = getResourceWriter(writer); resourceWriter.beginResponse(getResponse()); - WebdavResource resource = root; - Map rootPermissions = new HashMap<>(); - rootPermissions.put("canRead", resource.canRead(getUser(), false)); - rootPermissions.put("canUpload", resource.canCreate(getUser(), false)); - rootPermissions.put("canEdit", resource.canWrite(getUser(), false)); - rootPermissions.put("canDelete", resource.canDelete(getUser(), false)); - rootPermissions.put("canRename", resource.canRename(getUser(), false)); + rootPermissions.put("canRead", root.canRead(getUser(), false)); + rootPermissions.put("canUpload", root.canCreate(getUser(), false)); + rootPermissions.put("canEdit", root.canWrite(getUser(), false)); + rootPermissions.put("canDelete", root.canDelete(getUser(), false)); + rootPermissions.put("canRename", root.canRename(getUser(), false)); resourceWriter.writeProperty("permissions", rootPermissions); - if (resource.isCollection()) + if (root.isCollection()) { - Collection listPaths = resource.listNames(); // 17749 ArrayList resources = new ArrayList<>(); + var children = root.list(); // 17749 // Build resource set - for (String p : listPaths) + for (var child : children) { - if (p.startsWith(".")) + if (child.getName().startsWith(".")) continue; - resource = resolvePath(root.getPath().append(p)); - if (resource != null && resource.canList(getUser(), true)) + if (child.canList(getUser(), true)) { - if (resource.isCollection()) + if (child.isCollection()) { if (form.includeCollections()) - resources.add(resource); + resources.add(child); } - else + else if (child.exists()) { - resources.add(resource); + resources.add(child); } } } @@ -2761,8 +2753,9 @@ public void writeProperties(WebdavResource resource, Find type, List pro json.key("id").value(resource.getPath()); String displayName = resource.getPath().isEmpty() ? "/" : resource.getName(); json.key("href").value(resource.getLocalHref(getViewContext())); - if (resource.getNioPath() != null) - json.key("dataFileUrl").value(FileUtil.pathToString(resource.getNioPath())); + var nioPath = resource.getNioPath(); + if (null != nioPath) + json.key("dataFileUrl").value(FileUtil.pathToString(nioPath)); json.key("text").value(displayName); json.key("iconHref").value(resource.getIconHref()); json.key("iconFontCls").value(resource.getIconFontCls()); @@ -3804,10 +3797,10 @@ public CopyAction() WebdavStatus doMethod() throws DavException, IOException { checkReadOnly(); -// checkLocked(); checkLocked(getDestinationPath()); - return copyResource(); + var depth = getDepthParameter(Integer.MAX_VALUE, Integer.MAX_VALUE, true); + return copyResource(depth.getKey()); } } @@ -3862,7 +3855,7 @@ WebdavStatus doMethod() throws DavException, IOException throw new DavException(WebdavStatus.SC_PRECONDITION_FAILED); if (dest.isCollection()) { - if (!_overwriteCollection) + if (!_overwriteCollection || !src.isCollection()) throw new DavException(WebdavStatus.SC_FORBIDDEN, "Cannot overwrite folder"); WebdavStatus ret = deleteResource(destinationPath); if (ret != WebdavStatus.SC_NO_CONTENT) @@ -5644,7 +5637,7 @@ Path getDestinationPath() if (null == result || nullDavFileInfo == result || null == result.resource) return null; - boolean isRoot = path.size() == 0; + boolean isRoot = path.isEmpty(); if (!isRoot && path.isDirectory() && result.resource.isFile()) return null; return result; @@ -5978,7 +5971,7 @@ private String generateNamespaceDeclarations() * * @return boolean true if the copy is successful */ - private WebdavStatus copyResource() throws DavException, IOException + private WebdavStatus copyResource(int depth) throws DavException, IOException { boolean overwrite = getOverwriteParameter(false); Path destinationPath = getDestinationPath(); @@ -5987,14 +5980,35 @@ private WebdavStatus copyResource() throws DavException, IOException _log.debug("Dest path :" + destinationPath); WebdavResource resource = resolvePath(); - if (null != resource && !resource.canRead(getUser(),true)) + WebdavResource destination = resolvePath(destinationPath); + + if (null == resource || null == destination) + throw new DavException(WebdavStatus.SC_FORBIDDEN); + + if (!resource.canRead(getUser(),true)) unauthorized(resource); - if (null == resource || !resource.exists()) + if (!destination.canWrite(getUser(),true)) + unauthorized(destination); + + if (destination.exists()) + { + if (!overwrite) + throw new DavException(WebdavStatus.SC_PRECONDITION_FAILED); + } + else + { + var parent = destination.parent(); + if (null != parent && !parent.isCollection()) + throw new DavException(WebdavStatus.SC_CONFLICT); + } + + if (!resource.exists()) + { + // https://www.rfc-editor.org/rfc/rfc2518#section-8.8.4 implies that this should not fail if overwrite=T + // IMHO we can make users do RMCOL/DELETE if that is the intentsion throw new DavException(WebdavStatus.SC_NOT_FOUND); + } - WebdavResource destination = resolvePath(destinationPath); - if (null == destination) - throw new DavException(WebdavStatus.SC_FORBIDDEN); checkAllowedFileName(destination.getName()); WebdavStatus successStatus = destination.exists() ? WebdavStatus.SC_NO_CONTENT : WebdavStatus.SC_CREATED; @@ -6027,7 +6041,7 @@ private WebdavStatus copyResource() throws DavException, IOException // Copying source to destination LinkedHashMap errorList = new LinkedHashMap<>(); - WebdavStatus ret = copyResource(resource, errorList, destinationPath); + WebdavStatus ret = copyResource(depth, resource, errorList, destinationPath); boolean result = ret == null; if ((!result) || (!errorList.isEmpty())) @@ -6048,7 +6062,7 @@ private WebdavStatus copyResource() throws DavException, IOException * during the copy operation * @param destPath Destination path */ - private WebdavStatus copyResource(WebdavResource src, Map errorList, Path destPath) throws DavException + private WebdavStatus copyResource(int depth, WebdavResource src, Map errorList, Path destPath) throws DavException { _log.debug("Copy: " + src.getPath() + " To: " + destPath); @@ -6061,6 +6075,8 @@ private WebdavStatus copyResource(WebdavResource src, Map err errorList.put(dest.getPath(), WebdavStatus.SC_CONFLICT); return WebdavStatus.SC_CONFLICT; } + if (depth == 0) + return null; try { @@ -6068,7 +6084,7 @@ private WebdavStatus copyResource(WebdavResource src, Map err for (WebdavResource child : children) { Path childDest = dest.getPath().append(child.getName()); - copyResource(child, errorList, childDest); + copyResource(depth-1, child, errorList, childDest); } } catch (Exception e) @@ -6081,12 +6097,12 @@ private WebdavStatus copyResource(WebdavResource src, Map err { try { - boolean exists = dest.getFile().exists(); - FileUtil.copyFile(src.getFile(), dest.getFile()); + boolean exists = dest.exists(); + dest.copyFrom(getUser(), src); if (exists) - dest.notify(getViewContext(), "overwrite: copied from " + src.getFile().getPath()); + dest.notify(getViewContext(), "overwrite: copied from " + src.getPath()); else - dest.notify(getViewContext(), "create: copied from " + src.getFile().getPath()); + dest.notify(getViewContext(), "create: copied from " + src.getPath()); addToIndex(dest); } catch (IOException ex) @@ -6280,13 +6296,6 @@ public String toString() } } - - /** - * Default depth is infinite. - */ - private static final int INFINITY = 3; // To limit tree browsing a bit - - enum Find { FIND_BY_PROPERTY, @@ -6353,7 +6362,7 @@ public int available() throws IOException { String method = getRequest().getMethod(); // GET, HEAD, and MKCOL request have no inputstream and are correctly 'available=0' in http, but in https were showing 'available>0' (fix for ISSUE: 25318 and 25437) - if ( !"GET".equals(method) && !"HEAD".equals(method) && !"MKCOL".equals(method)) { + if (!"GET".equals(method) && !"HEAD".equals(method)) { return super.available(); } return 0; diff --git a/filecontent/src/org/labkey/filecontent/FileRootMaintenanceTask.java b/filecontent/src/org/labkey/filecontent/FileRootMaintenanceTask.java index 6832b72d46d..ffaeab44815 100644 --- a/filecontent/src/org/labkey/filecontent/FileRootMaintenanceTask.java +++ b/filecontent/src/org/labkey/filecontent/FileRootMaintenanceTask.java @@ -67,6 +67,8 @@ public void run(Logger log) Container c = ContainerManager.getForId(record.entityId()); if (c != null) { + if (service.isCloudRoot(c)) + return; File root = service.getFileRoot(c); Long size = null != root && root.isDirectory() ? FileUtils.sizeOfDirectory(root) : null; long current = HeartBeat.currentTimeMillis();