Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,21 @@ public BasicFolderResourceImpl(String path) {
this.isAutoPub=dotDavHelper.isAutoPub(path);
}

public Resource createNew(String newName, final InputStream in, final Long length, final String contentType) throws IOException, DotRuntimeException {
/**
* Creates a file named {@code newName} in this folder. A temp-style name goes to temp storage
* and must be a single plain path segment; any other name is stored as content.
*
* @param newName the file name received from the WebDAV client
* @param in the file content
* @param length the content length, if known
* @param contentType the content type, if known
* @return the created resource
* @throws IOException if the content cannot be read or written
* @throws DotRuntimeException if the file cannot be created
* @throws BadRequestException (HTTP 400) when a temp-style name is not a single plain path segment
*/
public Resource createNew(String newName, final InputStream in, final Long length, final String contentType)
throws IOException, DotRuntimeException, BadRequestException {

if(newName.matches("^\\.(.*)-Spotlight$")){
// http://jira.dotmarketing.net/browse/DOTCMS-7285
Expand Down Expand Up @@ -83,6 +97,7 @@ public Resource createNew(String newName, final InputStream in, final Long lengt
}
} else {

DotWebdavHelper.requirePlainSegment(this, newName);
return this.createNewTemporalResource(newName, in);
}
} // createNew.
Expand Down
118 changes: 117 additions & 1 deletion dotCMS/src/main/java/com/dotmarketing/webdav/DotWebdavHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.bradmcevoy.http.Request;
import com.bradmcevoy.http.Request.Method;
import com.bradmcevoy.http.Resource;
import com.bradmcevoy.http.exceptions.BadRequestException;
import com.dotmarketing.beans.Host;
import com.dotmarketing.beans.Identifier;
import com.dotmarketing.beans.Permission;
Expand Down Expand Up @@ -391,14 +392,28 @@ public Folder loadFolder(String url,User user) throws IOException{
return folder;
}

/**
* Resolves a temporary WebDAV resource from a request-derived path, confined to the temp
* directory. The path is built under {@link #getTempDir()} and then checked for containment:
* a path that resolves outside the temp root (for example through a {@code ..} traversal) is
* rejected and {@code null} is returned, so the caller treats it as "not found".
*
* @param url the request URL (endpoint mapping is stripped internally)
* @return the temp {@link File}, or {@code null} when the path escapes the temp root
*/
public File loadTempFile(String url){
try {
url = stripMapping(url);
} catch (IOException e) {
Logger.error( this, "Error happened with uri: [" + url + "]", e);
}
Logger.debug(this, "Getting temp file from path " + url);
return new File(getTempDir().getPath() + url);
final File tempFile = new File(getTempDir().getPath() + url);
if (!isWithinTempDir(tempFile)) {
Logger.warn(this, "Rejected a WebDAV temp path outside the temp directory");
return null;
}
return tempFile;
}

/**
Expand Down Expand Up @@ -507,6 +522,81 @@ public File getTempDir () {
return new File(ConfigUtils.getAssetTempPath());
}

/**
* Verifies that {@code candidate} resolves to a location strictly inside the WebDAV temp
* directory ({@link #getTempDir()}). The temp directory itself is not a valid temp resource.
* Canonicalization collapses {@code .} and {@code ..} segments and resolves symlinks, so the
* check holds however the path is written. Fails closed: any canonicalization error is treated
* as "not contained".
*
* @param candidate the {@link File} built from request-derived input
* @return {@code true} only when the canonical candidate is a descendant of the temp directory
*/
private boolean isWithinTempDir(final File candidate) {
try {
final String root = getTempDir().getCanonicalPath();
return candidate.getCanonicalPath().startsWith(root + File.separator);
} catch (IOException e) {
Logger.error(this, "WebDAV temp-path canonicalization failed; rejecting", e);
return false;
}
}

/**
* Tells whether a client-supplied name can be used as a child name in temp storage: a single
* plain path segment that is not empty, not {@code .} or {@code ..}, and contains no path
* separator or NUL character.
*
* @param name the name received from the WebDAV client
* @return {@code true} when the name is a single plain path segment
*/
static boolean isPlainSegment(final String name) {
return name != null
&& !name.isEmpty()
&& !".".equals(name)
&& !"..".equals(name)
&& name.indexOf('/') < 0
&& name.indexOf('\\') < 0
&& name.indexOf('\0') < 0;
}

/**
* Rejects a client-supplied name that is not a single plain path segment, before it reaches
* temp storage.
*
* @param resource the resource handling the request, reported in the rejection
* @param name the name received from the WebDAV client
* @throws BadRequestException (HTTP 400) when the name is not a single plain path segment
*/
static void requirePlainSegment(final Resource resource, final String name) throws BadRequestException {
if (!isPlainSegment(name)) {
Logger.warn(DotWebdavHelper.class, "Rejected a WebDAV name that is not a single path segment");
throw new BadRequestException(resource, "Invalid name");
}
}

/**
* Resolves the target of a write into temp storage: {@code name} inside {@code folder}. The name
* must be a single plain path segment and the target must stay strictly inside the temp
* directory.
*
* @param resource the resource handling the request, reported in the rejection
* @param folder the temp folder receiving the write
* @param name the name received from the WebDAV client
* @return the target {@link File}
* @throws BadRequestException (HTTP 400) when the name or the target is not valid
*/
File resolveTempChild(final Resource resource, final File folder, final String name)
throws BadRequestException {
requirePlainSegment(resource, name);
final File target = new File(folder, name);
if (!isWithinTempDir(target)) {
Logger.warn(this, "Rejected a WebDAV temp target outside the temp directory");
throw new BadRequestException(resource, "Invalid destination");
}
return target;
}

public String getHostName ( String uri ) {
try {
return getHostname(stripMapping(uri));
Expand All @@ -523,6 +613,15 @@ public boolean isTempResource(String path){
return false;
}

/**
* Creates a temporary WebDAV folder under {@link #getTempDir()}, confined to the temp directory.
* A {@code path} that resolves outside the temp root (for example through a {@code ..} traversal)
* is rejected before any directory is created.
*
* @param path the request-derived folder path (endpoint mapping is stripped internally)
* @return the created temp folder
* @throws DotRuntimeException if the path escapes the temp root
*/
public File createTempFolder(String path){
try {
path = stripMapping(path);
Expand All @@ -536,6 +635,10 @@ public File createTempFolder(String path){
}
path = path.replace("/", File.separator);
File f = new File(getTempDir().getPath() + File.separator + path);
if (!isWithinTempDir(f)) {
Logger.warn(this, "Rejected a WebDAV temp folder outside the temp directory");
throw new DotRuntimeException("WebDAV temp folder path escapes the temp root");
}
f.mkdirs();
return f;
}
Expand Down Expand Up @@ -575,8 +678,21 @@ public File copyFileToTemp(IFileAsset file, File tempFolder) throws IOException{
return nf;
}

/**
* Creates a temporary WebDAV file under {@link #getTempDir()}, confined to the temp directory.
* A {@code path} that resolves outside the temp root (for example through a {@code ..} traversal)
* is rejected before any file or parent directory is created.
*
* @param path the request-derived file path, relative to the temp root
* @return the created temp file
* @throws IOException if the path escapes the temp root, or the file cannot be created
*/
public File createTempFile(String path) throws IOException{
File file = new File(getTempDir().getPath() + path);
if (!isWithinTempDir(file)) {
Logger.warn(this, "Rejected a WebDAV temp file outside the temp directory");
throw new IOException("WebDAV temp file path escapes the temp root");
}
String p = file.getPath().substring(0,file.getPath().lastIndexOf(File.separator));
File f = new File(p);
f.mkdirs();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,21 @@ public FolderResourceImpl(Folder folder, String path) {
this.hostAPI = APILocator.getHostAPI();
}

/* (non-Javadoc)
* @see com.bradmcevoy.http.MakeCollectionableResource#createCollection(java.lang.String)
/**
* Creates a folder named {@code newName} inside this folder. A temp-style name goes to temp
* storage and must be a single plain path segment.
*
* @param newName the folder name received from the WebDAV client
* @return the created folder
* @throws DotRuntimeException if the folder cannot be created
* @throws BadRequestException (HTTP 400) when a temp-style name is not a single plain path segment
*/
public CollectionResource createCollection(String newName) throws DotRuntimeException {
public CollectionResource createCollection(String newName) throws DotRuntimeException, BadRequestException {

User user=(User)HttpManager.request().getAuthorization().getTag();
String folderPath ="";
if(dotDavHelper.isTempResource(newName)){
DotWebdavHelper.requirePlainSegment(this, newName);
Host host;
try {
host = hostAPI.find(folder.getHostId(), user, false);
Expand Down Expand Up @@ -259,6 +266,7 @@ public void copyTo(CollectionResource collRes, String name) throws NotAuthorized
User user=(User)HttpManager.request().getAuthorization().getTag();

if(collRes instanceof TempFolderResourceImpl){
DotWebdavHelper.requirePlainSegment(this, name);
TempFolderResourceImpl tr = (TempFolderResourceImpl)collRes;
try {
dotDavHelper.copyFolderToTemp(folder, tr.getFolder(), user, name, isAutoPub, lang);
Expand Down Expand Up @@ -289,13 +297,20 @@ public void copyTo(CollectionResource collRes, String name) throws NotAuthorized
}
}

/* (non-Javadoc)
* @see com.bradmcevoy.http.MoveableResource#moveTo(com.bradmcevoy.http.CollectionResource, java.lang.String)
/**
* Moves this folder into {@code collRes} under {@code name}. A move that ends in temp storage
* requires {@code name} to be a single plain path segment.
*
* @param collRes the destination collection
* @param name the destination name received from the WebDAV client
* @throws DotRuntimeException if the move fails
* @throws BadRequestException (HTTP 400) when a temp destination name is not a single plain path segment
*/
public void moveTo(CollectionResource collRes, String name) throws DotRuntimeException{
public void moveTo(CollectionResource collRes, String name) throws DotRuntimeException, BadRequestException {
User user=(User)HttpManager.request().getAuthorization().getTag();
if(collRes instanceof TempFolderResourceImpl){
Logger.debug(this, "Webdav clients wants to move a file from dotcms to a temporary storage but we don't allow this in fear that the transaction may break and delete a file from dotcms");
DotWebdavHelper.requirePlainSegment(this, name);
TempFolderResourceImpl tr = (TempFolderResourceImpl)collRes;
try {
dotDavHelper.copyFolderToTemp(folder, tr.getFolder(), user, name, isAutoPub, lang);
Expand All @@ -306,6 +321,7 @@ public void moveTo(CollectionResource collRes, String name) throws DotRuntimeExc
}else if(collRes instanceof FolderResourceImpl){
FolderResourceImpl fr = (FolderResourceImpl)collRes;
if(dotDavHelper.isTempResource(name)){
DotWebdavHelper.requirePlainSegment(this, name);
Host host;
String folderPath = "";
try {
Expand Down Expand Up @@ -401,9 +417,23 @@ public Long getMaxAgeSeconds(Auth arg0) {
}


/**
* Creates a folder named {@code name} and locks it. The lock interface only allows
* {@link NotAuthorizedException}, so an invalid temp-style name is reported as a runtime error.
*
* @param name the folder name received from the WebDAV client
* @param timeout the requested lock timeout
* @param lockInfo the requested lock details
* @return the lock token of the created folder
* @throws NotAuthorizedException if the lock is not authorized
*/
public LockToken createAndLock(String name, LockTimeout timeout, LockInfo lockInfo)
throws NotAuthorizedException {
createCollection(name);
try {
createCollection(name);
} catch (BadRequestException e) {
throw new DotRuntimeException(e.getReason(), e);
}
return lock(timeout, lockInfo).getLockToken();
}

Expand Down
28 changes: 26 additions & 2 deletions dotCMS/src/main/java/com/dotmarketing/webdav/HostResourceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,19 @@ public Date getCreateDate() {
return host.getModDate();
}

public CollectionResource createCollection(String newName) throws DotRuntimeException {
/**
* Creates a folder named {@code newName} at the root of this site. A temp-style name goes to
* temp storage and must be a single plain path segment.
*
* @param newName the folder name received from the WebDAV client
* @return the created folder
* @throws DotRuntimeException if the folder cannot be created
* @throws BadRequestException (HTTP 400) when a temp-style name is not a single plain path segment
*/
public CollectionResource createCollection(String newName) throws DotRuntimeException, BadRequestException {
User user=(User)HttpManager.request().getAuthorization().getTag();
if(dotDavHelper.isTempResource(newName)){
DotWebdavHelper.requirePlainSegment(this, newName);
File f = dotDavHelper.createTempFolder(File.separator + host.getHostname() + File.separator + newName);
TempFolderResourceImpl tr = new TempFolderResourceImpl(f.getPath(),f ,isAutoPub);
return tr;
Expand All @@ -275,9 +285,23 @@ public CollectionResource createCollection(String newName) throws DotRuntimeExce
}
}

/**
* Creates a folder named {@code name} and locks it. The lock interface only allows
* {@link NotAuthorizedException}, so an invalid temp-style name is reported as a runtime error.
*
* @param name the folder name received from the WebDAV client
* @param timeout the requested lock timeout
* @param lockInfo the requested lock details
* @return the lock token of the created folder
* @throws NotAuthorizedException if the lock is not authorized
*/
public LockToken createAndLock(String name, LockTimeout timeout, LockInfo lockInfo)
throws NotAuthorizedException {
createCollection(name);
try {
createCollection(name);
} catch (BadRequestException e) {
throw new DotRuntimeException(e.getReason(), e);
}
return lock(timeout, lockInfo).getLockToken();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import com.bradmcevoy.http.Request;
import com.bradmcevoy.http.Request.Method;
import com.bradmcevoy.http.Resource;
import com.bradmcevoy.http.exceptions.BadRequestException;
import com.bradmcevoy.http.exceptions.NotAuthorizedException;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.business.Role;
import com.dotmarketing.business.web.WebAPILocator;
import com.dotmarketing.util.Config;
Expand Down Expand Up @@ -81,11 +83,17 @@ public LanguageFolderResourceImpl(String path) {
}
}

/* (non-Javadoc)
* @see com.bradmcevoy.http.MakeCollectionableResource#createCollection(java.lang.String)
/**
* Creates a temp folder for a temp-style {@code newName} at the language root; any other name is
* not supported here. The temp-style name must be a single plain path segment.
*
* @param newName the folder name received from the WebDAV client
* @return the created temp folder, or {@code null} when the name is not supported here
* @throws BadRequestException (HTTP 400) when a temp-style name is not a single plain path segment
*/
public CollectionResource createCollection(String newName) {
public CollectionResource createCollection(String newName) throws BadRequestException {
if(dotDavHelper.isTempResource(newName) && isLanguageRoot){
DotWebdavHelper.requirePlainSegment(this, newName);
dotDavHelper.createTempFolder(File.separator + "system" + File.separator + "languages" + File.separator + newName);
File f = new File(File.separator + "system" + File.separator + "languages");
TempFolderResourceImpl tr = new TempFolderResourceImpl(f.getPath(),f ,true);
Expand Down Expand Up @@ -374,9 +382,23 @@ public Long getMaxAgeSeconds(Auth arg0) {
return (long)60;
}

/**
* Creates a temp folder named {@code name} and locks it. The lock interface only allows
* {@link NotAuthorizedException}, so an invalid temp-style name is reported as a runtime error.
*
* @param name the folder name received from the WebDAV client
* @param timeout the requested lock timeout
* @param lockInfo the requested lock details
* @return the lock token of the created folder
* @throws NotAuthorizedException if the lock is not authorized
*/
public LockToken createAndLock(String name, LockTimeout timeout, LockInfo lockInfo)
throws NotAuthorizedException {
createCollection(name);
try {
createCollection(name);
} catch (BadRequestException e) {
throw new DotRuntimeException(e.getReason(), e);
}
return lock(timeout, lockInfo).getLockToken();
}

Expand Down
Loading
Loading