commit message

This commit is contained in:
zhouxin5253@163.com 2021-01-16 10:20:33 +08:00
commit 21281d05fe
68 changed files with 9430 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

4
Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM java:8
VOLUME /tmp
ADD webdav-teambition.jar /webdav-teambition.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/webdav-teambition.jar"]

310
mvnw vendored Normal file
View File

@ -0,0 +1,310 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# 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
#
# https://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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
mvnw.cmd vendored Normal file
View File

@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

57
pom.xml Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.github.zxbu</groupId>
<artifactId>webdav-teambition</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>webdav-teambition</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,48 @@
package com.github.zxbu.webdavteambition;
import com.github.zxbu.webdavteambition.store.TeambitionFileSystemStore;
import net.sf.webdav.LocalFileSystemStore;
import net.sf.webdav.WebdavServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.support.ErrorPageFilter;
import org.springframework.context.annotation.Bean;
import java.util.LinkedHashMap;
import java.util.Map;
@SpringBootApplication
public class WebdavTeambitionApplication {
public static void main(String[] args) {
SpringApplication.run(WebdavTeambitionApplication.class, args);
}
@Bean
public ServletRegistrationBean<WebdavServlet> myServlet(){
ServletRegistrationBean<WebdavServlet> servletRegistrationBean = new ServletRegistrationBean<>(new WebdavServlet(), "/*");
Map<String, String> inits = new LinkedHashMap<>();
inits.put("ResourceHandlerImplementation", TeambitionFileSystemStore.class.getName());
// inits.put("ResourceHandlerImplementation", LocalFileSystemStore.class.getName());
inits.put("rootpath", "./");
inits.put("storeDebug", "1");
servletRegistrationBean.setInitParameters(inits);
return servletRegistrationBean;
}
@Bean
public ErrorPageFilter errorPageFilter() {
return new ErrorPageFilter();
}
@Bean
public FilterRegistrationBean disableSpringBootErrorFilter(ErrorPageFilter filter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
}

View File

@ -0,0 +1,156 @@
package com.github.zxbu.webdavteambition.client;
import com.github.zxbu.webdavteambition.config.TeambitionProperties;
import com.github.zxbu.webdavteambition.util.JsonUtil;
import okhttp3.*;
import okio.BufferedSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
public class TeambitionClient {
private static final Logger LOGGER = LoggerFactory.getLogger(TeambitionClient.class);
private OkHttpClient okHttpClient;
private TeambitionProperties teambitionProperties;
public TeambitionClient(OkHttpClient okHttpClient, TeambitionProperties teambitionProperties) {
this.okHttpClient = okHttpClient;
this.teambitionProperties = teambitionProperties;
}
public void init() {
if (getOrgId() == null || getRootId() == null || getDriveId() == null || getSpaceId() == null) {
String personalJson = get("https://www.teambition.com/api/organizations/personal", Collections.emptyMap());
String orgId = (String) JsonUtil.getJsonNodeValue(personalJson, "_id");
teambitionProperties.setOrgId(orgId);
String memberId = (String) JsonUtil.getJsonNodeValue(personalJson, "_creatorId");
String orgJson = get("/pan/api/orgs/" + orgId, Collections.singletonMap("orgId", orgId));
String driveId = (String) JsonUtil.getJsonNodeValue(orgJson, "data.driveId");
teambitionProperties.setDriveId(driveId);
Map<String, String> params = new LinkedHashMap<>();
params.put("orgId", orgId);
params.put("memberId", memberId);
String spacesJson = get("/pan/api/spaces", params);
String rootId = (String) JsonUtil.getJsonNodeValue(spacesJson, "[0].rootId");
String spaceId = (String) JsonUtil.getJsonNodeValue(spacesJson, "[0].spaceId");
teambitionProperties.setRootId(rootId);
teambitionProperties.setSpaceId(spaceId);
}
}
public String getOrgId() {
return teambitionProperties.getOrgId();
}
public String getDriveId() {
return teambitionProperties.getDriveId();
}
public String getSpaceId() {
return teambitionProperties.getSpaceId();
}
public String getRootId() {
return teambitionProperties.getRootId();
}
public InputStream download(String url) {
Request request = new Request.Builder().url(url).build();
Response response = null;
try {
response = okHttpClient.newCall(request).execute();
return response.body().byteStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void upload(String url, byte[] bytes, final int offset, final int byteCount) {
Request request = new Request.Builder()
.put(RequestBody.create(MediaType.parse(""), bytes, offset, byteCount))
.url(url).build();
try (Response response = okHttpClient.newCall(request).execute()){
LOGGER.info("post {}, code {}", url, response.code());
if (!response.isSuccessful()) {
LOGGER.error("请求失败url={}, code={}, body={}", url, response.code(), response.body().string());
throw new RuntimeException("请求失败:" + url);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String post(String url, Object body) {
Request request = new Request.Builder()
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JsonUtil.toJson(body)))
.url(getTotalUrl(url)).build();
try (Response response = okHttpClient.newCall(request).execute()){
LOGGER.info("post {}, code {}", url, response.code());
if (!response.isSuccessful()) {
LOGGER.error("请求失败url={}, code={}, body={}", url, response.code(), response.body().string());
throw new RuntimeException("请求失败:" + url);
}
return toString(response.body());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String put(String url, Object body) {
Request request = new Request.Builder()
.put(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JsonUtil.toJson(body)))
.url(getTotalUrl(url)).build();
try (Response response = okHttpClient.newCall(request).execute()){
LOGGER.info("put {}, code {}", url, response.code());
if (!response.isSuccessful()) {
LOGGER.error("请求失败url={}, code={}, body={}", url, response.code(), response.body().string());
throw new RuntimeException("请求失败:" + url);
}
return toString(response.body());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String get(String url, Map<String, String> params) {
try {
HttpUrl.Builder urlBuilder = HttpUrl.parse(getTotalUrl(url)).newBuilder();
params.forEach(urlBuilder::addQueryParameter);
Request request = new Request.Builder().get().url(urlBuilder.build()).build();
try (Response response = okHttpClient.newCall(request).execute()){
LOGGER.info("get {}, code {}", urlBuilder.build(), response.code());
if (!response.isSuccessful()) {
throw new RuntimeException("请求失败:" + urlBuilder.build().toString());
}
return toString(response.body());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String toString(ResponseBody responseBody) throws IOException {
if (responseBody == null) {
return null;
}
return responseBody.string();
}
private String getTotalUrl(String url) {
if (url.startsWith("http")) {
return url;
}
return teambitionProperties.getUrl() + url;
}
}

View File

@ -0,0 +1,77 @@
package com.github.zxbu.webdavteambition.config;
import com.github.zxbu.webdavteambition.client.TeambitionClient;
import com.github.zxbu.webdavteambition.store.TeambitionFileSystemStore;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Configuration
@EnableConfigurationProperties(TeambitionProperties.class)
public class TeambitionAutoConfig implements ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(TeambitionAutoConfig.class);
@Autowired
private TeambitionProperties teambitionProperties;
@Bean
public TeambitionClient teambitionClient(ApplicationContext applicationContext) throws Exception {
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
request = request.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", teambitionProperties.getAgent())
.build();
return chain.proceed(request);
}
}).cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
if (StringUtils.hasLength(teambitionProperties.getCookies())) {
// do nothing
}
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
String cookies = teambitionProperties.getCookies();
String[] cookieSplit = cookies.split("; ");
List<Cookie> cookieList = new ArrayList<>(cookieSplit.length);
for (String cookie : cookieSplit) {
Cookie parse = Cookie.parse(url, cookie);
cookieList.add(parse);
}
return cookieList;
}
}).build();
TeambitionClient teambitionClient = new TeambitionClient(okHttpClient, teambitionProperties);
try (Response response = okHttpClient.newCall(new Request.Builder().get().url(teambitionProperties.getUrl() + "/pan/api").build()).execute()) {
if (response.isSuccessful()) {
LOGGER.info("TeambitionClient 启动成功");
}
}
teambitionClient.init();
return teambitionClient;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
TeambitionFileSystemStore.setApplicationContext(applicationContext);
}
}

View File

@ -0,0 +1,70 @@
package com.github.zxbu.webdavteambition.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "teambition", ignoreUnknownFields = true)
public class TeambitionProperties {
private String url = "https://pan.teambition.com";
private String cookies;
private String agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36";
private String orgId;
private String driveId;
private String spaceId;
private String rootId;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getCookies() {
return cookies;
}
public void setCookies(String cookies) {
this.cookies = cookies;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public String getDriveId() {
return driveId;
}
public void setDriveId(String driveId) {
this.driveId = driveId;
}
public String getSpaceId() {
return spaceId;
}
public String getAgent() {
return agent;
}
public void setAgent(String agent) {
this.agent = agent;
}
public void setSpaceId(String spaceId) {
this.spaceId = spaceId;
}
public String getRootId() {
return rootId;
}
public void setRootId(String rootId) {
this.rootId = rootId;
}
}

View File

@ -0,0 +1,40 @@
package com.github.zxbu.webdavteambition.model;
public class BaseQuery extends Page{
private String orgId;
private String driveId;
private String spaceId;
private String parentId;
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public String getDriveId() {
return driveId;
}
public void setDriveId(String driveId) {
this.driveId = driveId;
}
public String getSpaceId() {
return spaceId;
}
public void setSpaceId(String spaceId) {
this.spaceId = spaceId;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
}

View File

@ -0,0 +1,76 @@
package com.github.zxbu.webdavteambition.model;
public class CreateFileRequest {
private String ccpParentId;
private String checkNameMode = "refuse";
private String driveId;
private String name;
private String orgId;
private String parentId;
private String spaceId;
private String type;
public String getCheckNameMode() {
return checkNameMode;
}
public void setCheckNameMode(String checkNameMode) {
this.checkNameMode = checkNameMode;
}
public String getDriveId() {
return driveId;
}
public void setDriveId(String driveId) {
this.driveId = driveId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getSpaceId() {
return spaceId;
}
public void setSpaceId(String spaceId) {
this.spaceId = spaceId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCcpParentId() {
return ccpParentId;
}
public void setCcpParentId(String ccpParentId) {
this.ccpParentId = ccpParentId;
}
}

View File

@ -0,0 +1,5 @@
package com.github.zxbu.webdavteambition.model;
public enum FileType {
folder, file;
}

View File

@ -0,0 +1,51 @@
package com.github.zxbu.webdavteambition.model;
import java.util.List;
public class MoveRequest {
private String driveId;
private String parentId;
private String orgId;
private boolean sameLevel = false;
private List<MoveRequestId> ids;
public String getDriveId() {
return driveId;
}
public void setDriveId(String driveId) {
this.driveId = driveId;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public boolean isSameLevel() {
return sameLevel;
}
public void setSameLevel(boolean sameLevel) {
this.sameLevel = sameLevel;
}
public List<MoveRequestId> getIds() {
return ids;
}
public void setIds(List<MoveRequestId> ids) {
this.ids = ids;
}
}

View File

@ -0,0 +1,22 @@
package com.github.zxbu.webdavteambition.model;
public class MoveRequestId {
private String ccpFileId;
private String id;
public String getCcpFileId() {
return ccpFileId;
}
public void setCcpFileId(String ccpFileId) {
this.ccpFileId = ccpFileId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}

View File

@ -0,0 +1,4 @@
package com.github.zxbu.webdavteambition.model;
public class NodeQuery extends BaseQuery {
}

View File

@ -0,0 +1,40 @@
package com.github.zxbu.webdavteambition.model;
public class Page {
private int offset;
private int limit;
private String orderBy;
private String orderDirection;
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public String getOrderBy() {
return orderBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
public String getOrderDirection() {
return orderDirection;
}
public void setOrderDirection(String orderDirection) {
this.orderDirection = orderDirection;
}
}

View File

@ -0,0 +1,31 @@
package com.github.zxbu.webdavteambition.model;
public class PathInfo {
private String path;
private String parentPath;
private String name;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getParentPath() {
return parentPath;
}
public void setParentPath(String parentPath) {
this.parentPath = parentPath;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,24 @@
package com.github.zxbu.webdavteambition.model;
import java.util.List;
public class RemoveRequest {
private String orgId;
private List<String> nodeIds;
public List<String> getNodeIds() {
return nodeIds;
}
public void setNodeIds(List<String> nodeIds) {
this.nodeIds = nodeIds;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
}

View File

@ -0,0 +1,40 @@
package com.github.zxbu.webdavteambition.model;
public class RenameRequest {
private String ccpFileId;
private String driveId;
private String name;
private String orgId;
public String getCcpFileId() {
return ccpFileId;
}
public void setCcpFileId(String ccpFileId) {
this.ccpFileId = ccpFileId;
}
public String getDriveId() {
return driveId;
}
public void setDriveId(String driveId) {
this.driveId = driveId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
}

View File

@ -0,0 +1,49 @@
package com.github.zxbu.webdavteambition.model;
public class UploadFinalRequest {
private String ccpFileId;
private String driveId;
private String nodeId;
private String orgId;
private String uploadId;
public String getDriveId() {
return driveId;
}
public void setDriveId(String driveId) {
this.driveId = driveId;
}
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public String getUploadId() {
return uploadId;
}
public void setUploadId(String uploadId) {
this.uploadId = uploadId;
}
public String getCcpFileId() {
return ccpFileId;
}
public void setCcpFileId(String ccpFileId) {
this.ccpFileId = ccpFileId;
}
}

View File

@ -0,0 +1,67 @@
package com.github.zxbu.webdavteambition.model;
public class UploadPreInfo {
private String ccpParentId;
private int chunkCount;
private String contentType = "";
private String driveId;
private String name;
private int size;
private String type;
public int getChunkCount() {
return chunkCount;
}
public void setChunkCount(int chunkCount) {
this.chunkCount = chunkCount;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getDriveId() {
return driveId;
}
public void setDriveId(String driveId) {
this.driveId = driveId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCcpParentId() {
return ccpParentId;
}
public void setCcpParentId(String ccpParentId) {
this.ccpParentId = ccpParentId;
}
}

View File

@ -0,0 +1,51 @@
package com.github.zxbu.webdavteambition.model;
import java.util.List;
public class UploadPreRequest {
private String checkNameMode = "autoRename";
private String orgId;
private String parentId;
private String spaceId;
private List<UploadPreInfo> infos;
public String getCheckNameMode() {
return checkNameMode;
}
public void setCheckNameMode(String checkNameMode) {
this.checkNameMode = checkNameMode;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getSpaceId() {
return spaceId;
}
public void setSpaceId(String spaceId) {
this.spaceId = spaceId;
}
public List<UploadPreInfo> getInfos() {
return infos;
}
public void setInfos(List<UploadPreInfo> infos) {
this.infos = infos;
}
}

View File

@ -0,0 +1,15 @@
package com.github.zxbu.webdavteambition.model.result;
import java.util.List;
public class ListResult<T> {
private List<T> data;
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
}

View File

@ -0,0 +1,105 @@
package com.github.zxbu.webdavteambition.model.result;
import java.util.Date;
public class TFile {
private String kind;
private String nodeId;
private String name;
private Date created;
private Date updated;
private String parentId;
private String status;
private String downloadUrl;
private Long size;
private String ccpFileId;
private String ccpParentFileId;
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Long getSize() {
return size;
}
public String getDownloadUrl() {
return downloadUrl;
}
public void setDownloadUrl(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
public String getCcpFileId() {
return ccpFileId;
}
public void setCcpFileId(String ccpFileId) {
this.ccpFileId = ccpFileId;
}
public String getCcpParentFileId() {
return ccpParentFileId;
}
public void setCcpParentFileId(String ccpParentFileId) {
this.ccpParentFileId = ccpParentFileId;
}
public void setSize(Long size) {
this.size = size;
}
}

View File

@ -0,0 +1,60 @@
package com.github.zxbu.webdavteambition.model.result;
import java.util.List;
public class UploadPreResult {
private String ccpFileId;
private String nodeId;
private String name;
private String kind;
private String uploadId;
private List<String> uploadUrl;
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCcpFileId() {
return ccpFileId;
}
public void setCcpFileId(String ccpFileId) {
this.ccpFileId = ccpFileId;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getUploadId() {
return uploadId;
}
public void setUploadId(String uploadId) {
this.uploadId = uploadId;
}
public List<String> getUploadUrl() {
return uploadUrl;
}
public void setUploadUrl(List<String> uploadUrl) {
this.uploadUrl = uploadUrl;
}
}

View File

@ -0,0 +1,282 @@
package com.github.zxbu.webdavteambition.store;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.zxbu.webdavteambition.client.TeambitionClient;
import com.github.zxbu.webdavteambition.model.*;
import com.github.zxbu.webdavteambition.model.result.ListResult;
import com.github.zxbu.webdavteambition.model.result.TFile;
import com.github.zxbu.webdavteambition.model.result.UploadPreResult;
import com.github.zxbu.webdavteambition.util.JsonUtil;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class TeambitionClientService {
private static final Logger LOGGER = LoggerFactory.getLogger(TeambitionClientService.class);
private static ObjectMapper objectMapper = new ObjectMapper();
private static String rootPath = "/";
private static int chunkSize = 10485760; // 10MB
private TFile rootTFile = null;
private Map<String, TFile> nodeIdMap = new ConcurrentHashMap<>();
private final TeambitionClient client;
public TeambitionClientService(TeambitionClient teambitionClient) {
this.client = teambitionClient;
}
public List<TFile> getTFiles(String nodeId) {
NodeQuery nodeQuery = new NodeQuery();
nodeQuery.setOrgId(client.getOrgId());
nodeQuery.setOffset(0);
nodeQuery.setLimit(10000);
nodeQuery.setOrderBy("updateTime");
nodeQuery.setOrderDirection("desc");
nodeQuery.setDriveId(client.getDriveId());
nodeQuery.setSpaceId(client.getSpaceId());
nodeQuery.setParentId(nodeId);
String json = client.get("/pan/api/nodes", toMap(nodeQuery));
ListResult<TFile> tFileListResult = JsonUtil.readValue(json, new TypeReference<ListResult<TFile>>() {
});
return tFileListResult.getData();
}
private Map<String, String> toMap(Object o) {
try {
String json = objectMapper.writeValueAsString(o);
Map<String, Object> rawMap = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {
});
Map<String, String> stringMap = new LinkedHashMap<>();
rawMap.forEach((s, o1) -> {
if (o1 != null) {
stringMap.put(s, o1.toString());
}
});
return stringMap;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void uploadPre(String path, int size, InputStream inputStream) {
path = normalizingPath(path);
PathInfo pathInfo = getPathInfo(path);
TFile parent = getTFileByPath(pathInfo.getParentPath());
if (parent == null) {
return;
}
int chunkCount = (int) Math.ceil(((double) size) / chunkSize); // 进1法
UploadPreRequest uploadPreRequest = new UploadPreRequest();
uploadPreRequest.setOrgId(client.getOrgId());
uploadPreRequest.setParentId(parent.getNodeId());
uploadPreRequest.setSpaceId(client.getSpaceId());
UploadPreInfo uploadPreInfo = new UploadPreInfo();
uploadPreInfo.setCcpParentId(parent.getCcpFileId());
uploadPreInfo.setDriveId(client.getDriveId());
uploadPreInfo.setName(pathInfo.getName());
uploadPreInfo.setSize(size);
uploadPreInfo.setChunkCount(chunkCount);
uploadPreInfo.setType(FileType.file.name());
uploadPreRequest.setInfos(Collections.singletonList(uploadPreInfo));
LOGGER.info("开始上传文件,文件名:{},总大小:{}, 文件块数量:{}", path, size, chunkCount);
String json = client.post("/pan/api/nodes/file", uploadPreRequest);
List<UploadPreResult> uploadPreResultList = JsonUtil.readValue(json, new TypeReference<List<UploadPreResult>>() {
});
UploadPreResult uploadPreResult = uploadPreResultList.get(0);
List<String> uploadUrl = uploadPreResult.getUploadUrl();
LOGGER.info("文件预处理成功,开始上传。文件名:{}上传URL数量{}", path, uploadUrl.size());
byte[] buffer = new byte[chunkSize];
for (String oneUploadUrl : uploadUrl) {
try {
int read = IOUtils.read(inputStream, buffer, 0, buffer.length);
if (read == -1) {
return;
}
client.upload(oneUploadUrl, buffer, 0, read);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
UploadFinalRequest uploadFinalRequest = new UploadFinalRequest();
uploadFinalRequest.setCcpFileId(uploadPreResult.getCcpFileId());
uploadFinalRequest.setDriveId(client.getDriveId());
uploadFinalRequest.setNodeId(uploadPreResult.getNodeId());
uploadFinalRequest.setOrgId(client.getOrgId());
uploadFinalRequest.setUploadId(uploadPreResult.getUploadId());
client.post("/pan/api/nodes/complete", uploadFinalRequest);
LOGGER.info("文件上传成功。文件名:{}", path);
if (!uploadPreResult.getName().equals(pathInfo.getName())) {
LOGGER.info("上传文件名{}与原文件名{}不同,对文件进行重命名", uploadPreResult.getName(), pathInfo.getName());
RenameRequest renameRequest = new RenameRequest();
renameRequest.setCcpFileId(uploadPreResult.getCcpFileId());
renameRequest.setDriveId(client.getDriveId());
renameRequest.setOrgId(client.getOrgId());
renameRequest.setName(pathInfo.getName());
client.put("/pan/api/nodes/" + parent.getNodeId(), renameRequest);
}
clearCache(path);
}
public void rename(String sourcePath, String newName) {
sourcePath = normalizingPath(sourcePath);
TFile tFile = getTFileByPath(sourcePath);
RenameRequest renameRequest = new RenameRequest();
renameRequest.setCcpFileId(tFile.getCcpFileId());
renameRequest.setDriveId(client.getDriveId());
renameRequest.setOrgId(client.getOrgId());
renameRequest.setName(newName);
client.put("/pan/api/nodes/" + tFile.getParentId(), renameRequest);
clearCache(sourcePath);
}
public void move(String sourcePath, String targetPath) {
sourcePath = normalizingPath(sourcePath);
targetPath = normalizingPath(targetPath);
TFile sourceTFile = getTFileByPath(sourcePath);
TFile targetTFile = getTFileByPath(targetPath);
MoveRequest moveRequest = new MoveRequest();
moveRequest.setOrgId(client.getOrgId());
moveRequest.setDriveId(client.getDriveId());
moveRequest.setParentId(targetTFile.getNodeId());
MoveRequestId moveRequestId = new MoveRequestId();
moveRequestId.setCcpFileId(sourceTFile.getCcpFileId());
moveRequestId.setId(sourceTFile.getNodeId());
moveRequest.setIds(Collections.singletonList(moveRequestId));
client.post("/pan/api/nodes/move", moveRequest);
clearCache(sourcePath);
clearCache(targetPath);
}
public void remove(String path) {
path = normalizingPath(path);
TFile tFile = getTFileByPath(path);
if (tFile == null) {
return;
}
RemoveRequest removeRequest = new RemoveRequest();
removeRequest.setOrgId(client.getOrgId());
removeRequest.setNodeIds(Collections.singletonList(tFile.getNodeId()));
client.post("/pan/api/nodes/archive", removeRequest);
clearCache(path);
}
public void createFolder(String path) {
path = normalizingPath(path);
PathInfo pathInfo = getPathInfo(path);
TFile parent = getTFileByPath(pathInfo.getParentPath());
if (parent == null) {
return;
}
CreateFileRequest createFileRequest = new CreateFileRequest();
createFileRequest.setCcpParentId(parent.getCcpFileId());
createFileRequest.setDriveId(client.getDriveId());
createFileRequest.setName(pathInfo.getName());
createFileRequest.setOrgId(client.getOrgId());
createFileRequest.setParentId(parent.getNodeId());
createFileRequest.setSpaceId(client.getSpaceId());
createFileRequest.setType(FileType.folder.name());
client.post("/pan/api/nodes/folder", createFileRequest);
clearCache(path);
}
public TFile getTFileByPath(String path) {
path = normalizingPath(path);
return nodeIdMap.computeIfAbsent(path, this::getNodeIdByPath2);
}
public InputStream download(String path) {
String downloadUrl = getTFileByPath(path).getDownloadUrl();
return client.download(downloadUrl);
}
private TFile getNodeIdByPath2(String path) {
if (!StringUtils.hasLength(path)) {
path = rootPath;
}
if (path.equals(rootPath)) {
return getRootTFile();
}
PathInfo pathInfo = getPathInfo(path);
TFile tFile = getTFileByPath(pathInfo.getParentPath());
if (tFile == null ) {
return null;
}
return getNodeIdByParentId(tFile.getNodeId(), pathInfo.getName());
}
public PathInfo getPathInfo(String path) {
path = normalizingPath(path);
if (path.equals(rootPath)) {
PathInfo pathInfo = new PathInfo();
pathInfo.setPath(path);
pathInfo.setName(path);
return pathInfo;
}
int index = path.lastIndexOf("/");
String parentPath = path.substring(0, index + 1);
String name = path.substring(index+1);
PathInfo pathInfo = new PathInfo();
pathInfo.setPath(path);
pathInfo.setParentPath(parentPath);
pathInfo.setName(name);
return pathInfo;
}
private TFile getRootTFile() {
if (rootTFile == null) {
NodeQuery nodeQuery = new NodeQuery();
nodeQuery.setOrgId(client.getOrgId());
nodeQuery.setDriveId(client.getDriveId());
nodeQuery.setSpaceId(client.getSpaceId());
String json = client.get("/pan/api/nodes/" + client.getRootId(), toMap(nodeQuery));
rootTFile = JsonUtil.readValue(json, TFile.class);
}
return rootTFile;
}
private TFile getNodeIdByParentId(String parentId, String name) {
List<TFile> tFiles = getTFiles(parentId);
for (TFile tFile : tFiles) {
if (tFile.getName().equals(name)) {
return tFile;
}
}
return null;
}
private String normalizingPath(String path) {
path = path.replaceAll("//", "/");
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
return path;
}
private void clearCache(String path) {
nodeIdMap.remove(path);
}
}

View File

@ -0,0 +1,164 @@
package com.github.zxbu.webdavteambition.store;
import com.github.zxbu.webdavteambition.model.PathInfo;
import com.github.zxbu.webdavteambition.model.result.TFile;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.InputStream;
import java.security.Principal;
import java.util.Date;
import java.util.List;
public class TeambitionFileSystemStore implements IWebdavStore {
private static final Logger LOGGER = LoggerFactory.getLogger(TeambitionFileSystemStore.class);
private static ApplicationContext applicationContext;
private static TeambitionClientService teambitionClientService;
public TeambitionFileSystemStore(File file) {
}
public static void setApplicationContext(ApplicationContext applicationContext) {
TeambitionFileSystemStore.applicationContext = applicationContext;
TeambitionFileSystemStore.teambitionClientService = applicationContext.getBean(TeambitionClientService.class);
}
@Override
public void destroy() {
LOGGER.debug("destroy");
}
@Override
public ITransaction begin(Principal principal) {
LOGGER.debug("begin");
return null;
}
@Override
public void checkAuthentication(ITransaction transaction) {
LOGGER.debug("checkAuthentication");
}
@Override
public void commit(ITransaction transaction) {
LOGGER.debug("commit");
}
@Override
public void rollback(ITransaction transaction) {
LOGGER.debug("rollback");
}
@Override
public void createFolder(ITransaction transaction, String folderUri) {
LOGGER.info("createFolder {}", folderUri);
teambitionClientService.createFolder(folderUri);
}
@Override
public void createResource(ITransaction transaction, String resourceUri) {
LOGGER.info("createResource {}", resourceUri);
}
@Override
public InputStream getResourceContent(ITransaction transaction, String resourceUri) {
LOGGER.debug("getResourceContent: {}", resourceUri);
return teambitionClientService.download(resourceUri);
}
@Override
public long setResourceContent(ITransaction transaction, String resourceUri, InputStream content, String contentType, String characterEncoding) {
LOGGER.info("setResourceContent {}", resourceUri);
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
int contentLength = request.getContentLength();
if (contentLength <= 0) {
return 0;
}
teambitionClientService.uploadPre(resourceUri, contentLength, content);
return contentLength;
}
@Override
public String[] getChildrenNames(ITransaction transaction, String folderUri) {
LOGGER.debug("getChildrenNames: {}", folderUri);
TFile tFile = teambitionClientService.getTFileByPath(folderUri);
List<TFile> tFileList = teambitionClientService.getTFiles(tFile.getNodeId());
return tFileList.stream().map(TFile::getName).toArray(String[]::new);
}
@Override
public long getResourceLength(ITransaction transaction, String path) {
LOGGER.debug("getResourceLength: {}", path);
TFile tFile = teambitionClientService.getTFileByPath(path);
if (tFile == null || tFile.getSize() == null) {
return 384;
}
return tFile.getSize();
}
@Override
public void removeObject(ITransaction transaction, String uri) {
LOGGER.info("removeObject: {}", uri);
teambitionClientService.remove(uri);
}
@Override
public boolean moveObject(ITransaction transaction, String destinationPath, String sourcePath) {
LOGGER.info("moveObject, destinationPath={}, sourcePath={}", destinationPath, sourcePath);
PathInfo destinationPathInfo = teambitionClientService.getPathInfo(destinationPath);
PathInfo sourcePathInfo = teambitionClientService.getPathInfo(sourcePath);
// 名字相同说明是移动目录
if (sourcePathInfo.getName().equals(destinationPathInfo.getName())) {
teambitionClientService.move(sourcePath, destinationPathInfo.getParentPath());
} else {
if (!destinationPathInfo.getParentPath().equals(sourcePathInfo.getParentPath())) {
throw new RuntimeException("不支持目录和名字同时修改");
}
// 名字不同说明是修改名字不考虑目录和名字同时修改的情况
teambitionClientService.rename(sourcePath, destinationPathInfo.getName());
}
return true;
}
@Override
public StoredObject getStoredObject(ITransaction transaction, String uri) {
LOGGER.debug("getStoredObject: {}", uri);
TFile tFile = teambitionClientService.getTFileByPath(uri);
if (tFile != null) {
StoredObject so = new StoredObject();
so.setFolder(tFile.getKind().equalsIgnoreCase("folder"));
so.setResourceLength(getResourceLength(transaction, uri));
so.setCreationDate(tFile.getCreated());
so.setLastModified(tFile.getUpdated());
return so;
}
return null;
}
}

View File

@ -0,0 +1,143 @@
package com.github.zxbu.webdavteambition.util;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
public static String toJson(Object o) {
try {
return objectMapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T readValue(String json, TypeReference<T> valueTypeRef) {
try {
return objectMapper.readValue(json, valueTypeRef);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static <T> T readValue(String json, Class<T> valueType) {
try {
return objectMapper.readValue(json, valueType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static {
// 忽略未知的字段
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 读取不认识的枚举时当null值处理
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
}
public static Object getJsonNodeValue(String json, String path) {
try {
JsonNode jsonNode = getJsonNode(json);
List<PathToken> pathTokens = getPathTokens(path);
for (PathToken pathToken : pathTokens) {
if (pathToken.getType() == PathType.KEY) {
jsonNode = jsonNode.get(pathToken.getValue());
}
if (pathToken.getType() == PathType.NUMBER) {
jsonNode = jsonNode.get(Integer.parseInt(pathToken.getValue()));
}
}
return objectMapper.treeToValue(jsonNode, Object.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static List<PathToken> getPathTokens(String path) {
List<PathToken> pathTokenList = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean escape = false;
for (char c : path.toCharArray()) {
if (c == '[') {
if (sb.length() > 0) {
pathTokenList.add(new PathToken(PathType.KEY, sb.toString()));
}
sb.setLength(0);
} else if (c == ']') {
if (sb.length() > 0) {
pathTokenList.add(new PathToken(PathType.NUMBER, sb.toString()));
}
sb.setLength(0);
} else if (c == '.') {
if (escape) {
sb.append(c);
} else {
if (sb.length() > 0) {
pathTokenList.add(new PathToken(PathType.KEY, sb.toString()));
}
sb.setLength(0);
}
} else if (c == '\\') {
escape = true;
} else {
sb.append(c);
}
}
if (sb.length() > 0) {
pathTokenList.add(new PathToken(PathType.KEY, sb.toString()));
}
return pathTokenList;
}
private static JsonNode getJsonNode(String responseBody) {
try {
return objectMapper.readTree(responseBody);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
private enum PathType {
KEY, NUMBER;
}
private static class PathToken {
private PathType type; // 0 是key 1 是索引数字
private String value;
public PathToken(PathType type, String value) {
this.type = type;
this.value = value;
}
public PathType getType() {
return type;
}
public String getValue() {
return value;
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.exceptions.LockFailedException;
public interface IMethodExecutor {
void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException;
}

View File

@ -0,0 +1,13 @@
package net.sf.webdav;
public interface IMimeTyper {
/**
* Detect the mime type of this object
*
* @param transaction
* @param path
* @return
*/
String getMimeType(ITransaction transaction, String path);
}

View File

@ -0,0 +1,9 @@
package net.sf.webdav;
import java.security.Principal;
public interface ITransaction {
Principal getPrincipal();
}

View File

@ -0,0 +1,223 @@
/*
* $Header: /Users/ak/temp/cvs2svn/webdav-servlet/src/main/java/net/sf/webdav/IWebdavStore.java,v 1.1 2008-08-05 07:38:42 bauhardt Exp $
* $Revision: 1.1 $
* $Date: 2008-08-05 07:38:42 $
*
* ====================================================================
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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 net.sf.webdav;
import java.io.InputStream;
import java.security.Principal;
import net.sf.webdav.exceptions.WebdavException;
/**
* Interface for simple implementation of any store for the WebdavServlet
* <p>
* based on the BasicWebdavStore from Oliver Zeigermann, that was part of the
* Webdav Construcktion Kit from slide
*
*/
public interface IWebdavStore {
/**
* Life cycle method, called by WebdavServlet's destroy() method. Should be used to clean up resources.
*/
void destroy();
/**
* Indicates that a new request or transaction with this store involved has
* been started. The request will be terminated by either {@link #commit()}
* or {@link #rollback()}. If only non-read methods have been called, the
* request will be terminated by a {@link #commit()}. This method will be
* called by (@link WebdavStoreAdapter} at the beginning of each request.
*
*
* @param principal
* the principal that started this request or <code>null</code> if
* there is non available
*
* @throws WebdavException
*/
ITransaction begin(Principal principal);
/**
* Checks if authentication information passed in is valid. If not throws an
* exception.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
*/
void checkAuthentication(ITransaction transaction);
/**
* Indicates that all changes done inside this request shall be made
* permanent and any transactions, connections and other temporary resources
* shall be terminated.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
*
* @throws WebdavException
* if something goes wrong on the store level
*/
void commit(ITransaction transaction);
/**
* Indicates that all changes done inside this request shall be undone and
* any transactions, connections and other temporary resources shall be
* terminated.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
*
* @throws WebdavException
* if something goes wrong on the store level
*/
void rollback(ITransaction transaction);
/**
* Creates a folder at the position specified by <code>folderUri</code>.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param folderUri
* URI of the folder
* @throws WebdavException
* if something goes wrong on the store level
*/
void createFolder(ITransaction transaction, String folderUri);
/**
* Creates a content resource at the position specified by
* <code>resourceUri</code>.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param resourceUri
* URI of the content resource
* @throws WebdavException
* if something goes wrong on the store level
*/
void createResource(ITransaction transaction, String resourceUri);
/**
* Gets the content of the resource specified by <code>resourceUri</code>.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param resourceUri
* URI of the content resource
* @return input stream you can read the content of the resource from
* @throws WebdavException
* if something goes wrong on the store level
*/
InputStream getResourceContent(ITransaction transaction, String resourceUri);
/**
* Sets / stores the content of the resource specified by
* <code>resourceUri</code>.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param resourceUri
* URI of the resource where the content will be stored
* @param content
* input stream from which the content will be read from
* @param contentType
* content type of the resource or <code>null</code> if unknown
* @param characterEncoding
* character encoding of the resource or <code>null</code> if unknown
* or not applicable
* @return lenght of resource
* @throws WebdavException
* if something goes wrong on the store level
*/
long setResourceContent(ITransaction transaction, String resourceUri,
InputStream content, String contentType, String characterEncoding);
/**
* Gets the names of the children of the folder specified by
* <code>folderUri</code>.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param folderUri
* URI of the folder
* @return a (possibly empty) list of children, or <code>null</code> if the
* uri points to a file
* @throws WebdavException
* if something goes wrong on the store level
*/
String[] getChildrenNames(ITransaction transaction, String folderUri);
/**
* Gets the length of the content resource specified by
* <code>resourceUri</code>.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param resourceUri
* URI of the content resource
* @return length of the resource in bytes, <code>-1</code> declares this
* value as invalid and asks the adapter to try to set it from the
* properties if possible
* @throws WebdavException
* if something goes wrong on the store level
*/
long getResourceLength(ITransaction transaction, String path);
/**
* Removes the object specified by <code>uri</code>.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param uri
* URI of the object, i.e. content resource or folder
* @throws WebdavException
* if something goes wrong on the store level
*/
void removeObject(ITransaction transaction, String uri);
boolean moveObject(ITransaction transaction, String destinationPath, String sourcePath);
/**
* Gets the storedObject specified by <code>uri</code>
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param uri
* URI
* @return StoredObject
*/
StoredObject getStoredObject(ITransaction transaction, String uri);
}

View File

@ -0,0 +1,228 @@
/*
*
* Licensed 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 net.sf.webdav;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import net.sf.webdav.exceptions.UnauthenticatedException;
import net.sf.webdav.exceptions.WebdavException;
/**
* Reference Implementation of WebdavStore
*
* @author joa
* @author re
*/
public class LocalFileSystemStore implements IWebdavStore {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(LocalFileSystemStore.class);
private static int BUF_SIZE = 65536;
private File _root = null;
public LocalFileSystemStore(File root) {
_root = root;
}
public void destroy() {
;
}
public ITransaction begin(Principal principal) throws WebdavException {
LOG.info("LocalFileSystemStore.begin()");
if (!_root.exists()) {
if (!_root.mkdirs()) {
throw new WebdavException("root path: "
+ _root.getAbsolutePath()
+ " does not exist and could not be created");
}
}
// if (principal == null) {
// throw new UnauthenticatedException(WebdavStatus.SC_UNAUTHORIZED);
// }
return null;
}
public void checkAuthentication(ITransaction transaction)
throws SecurityException {
LOG.info("LocalFileSystemStore.checkAuthentication()");
// do nothing
// throw new UnauthenticatedException(WebdavStatus.SC_FORBIDDEN);
}
public void commit(ITransaction transaction) throws WebdavException {
// do nothing
LOG.info("LocalFileSystemStore.commit()");
}
public void rollback(ITransaction transaction) throws WebdavException {
// do nothing
LOG.info("LocalFileSystemStore.rollback()");
}
public void createFolder(ITransaction transaction, String uri)
throws WebdavException {
LOG.info("LocalFileSystemStore.createFolder(" + uri + ")");
File file = new File(_root, uri);
if (!file.mkdir())
throw new WebdavException("cannot create folder: " + uri);
}
public void createResource(ITransaction transaction, String uri)
throws WebdavException {
LOG.info("LocalFileSystemStore.createResource(" + uri + ")");
File file = new File(_root, uri);
try {
if (!file.createNewFile())
throw new WebdavException("cannot create file: " + uri);
} catch (IOException e) {
LOG
.error("LocalFileSystemStore.createResource(" + uri
+ ") failed");
throw new WebdavException(e);
}
}
public long setResourceContent(ITransaction transaction, String uri,
InputStream is, String contentType, String characterEncoding)
throws WebdavException {
LOG.info("LocalFileSystemStore.setResourceContent(" + uri + ")");
File file = new File(_root, uri);
try {
OutputStream os = new BufferedOutputStream(new FileOutputStream(
file), BUF_SIZE);
try {
int read;
byte[] copyBuffer = new byte[BUF_SIZE];
while ((read = is.read(copyBuffer, 0, copyBuffer.length)) != -1) {
os.write(copyBuffer, 0, read);
}
} finally {
try {
is.close();
} finally {
os.close();
}
}
} catch (IOException e) {
LOG.error("LocalFileSystemStore.setResourceContent(" + uri
+ ") failed");
throw new WebdavException(e);
}
long length = -1;
try {
length = file.length();
} catch (SecurityException e) {
LOG.error("LocalFileSystemStore.setResourceContent(" + uri
+ ") failed" + "\nCan't get file.length");
}
return length;
}
public String[] getChildrenNames(ITransaction transaction, String uri)
throws WebdavException {
LOG.info("LocalFileSystemStore.getChildrenNames(" + uri + ")");
File file = new File(_root, uri);
String[] childrenNames = null;
if (file.isDirectory()) {
File[] children = file.listFiles();
List<String> childList = new ArrayList<String>();
String name = null;
for (int i = 0; i < children.length; i++) {
name = children[i].getName();
childList.add(name);
LOG.info("Child " + i + ": " + name);
}
childrenNames = new String[childList.size()];
childrenNames = (String[]) childList.toArray(childrenNames);
}
return childrenNames;
}
public void removeObject(ITransaction transaction, String uri)
throws WebdavException {
File file = new File(_root, uri);
boolean success = file.delete();
LOG.info("LocalFileSystemStore.removeObject(" + uri + ")=" + success);
if (!success) {
throw new WebdavException("cannot delete object: " + uri);
}
}
@Override
public boolean moveObject(ITransaction transaction, String destinationPath, String path) {
return false;
}
public InputStream getResourceContent(ITransaction transaction, String uri)
throws WebdavException {
LOG.info("LocalFileSystemStore.getResourceContent(" + uri + ")");
File file = new File(_root, uri);
InputStream in;
try {
in = new BufferedInputStream(new FileInputStream(file));
} catch (IOException e) {
LOG.error("LocalFileSystemStore.getResourceContent(" + uri
+ ") failed");
throw new WebdavException(e);
}
return in;
}
public long getResourceLength(ITransaction transaction, String uri)
throws WebdavException {
LOG.info("LocalFileSystemStore.getResourceLength(" + uri + ")");
File file = new File(_root, uri);
return file.length();
}
public StoredObject getStoredObject(ITransaction transaction, String uri) {
StoredObject so = null;
File file = new File(_root, uri);
if (file.exists()) {
so = new StoredObject();
so.setFolder(file.isDirectory());
so.setLastModified(new Date(file.lastModified()));
so.setCreationDate(new Date(file.lastModified()));
so.setResourceLength(getResourceLength(transaction, uri));
}
return so;
}
}

View File

@ -0,0 +1,165 @@
/*
* ====================================================================
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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 net.sf.webdav;
import java.util.Date;
public class StoredObject {
private boolean isFolder;
private Date lastModified;
private Date creationDate;
private long contentLength;
private String mimeType;
private boolean isNullRessource;
/**
* Determines whether the StoredObject is a folder or a resource
*
* @return true if the StoredObject is a collection
*/
public boolean isFolder() {
return (isFolder);
}
/**
* Determines whether the StoredObject is a folder or a resource
*
* @return true if the StoredObject is a resource
*/
public boolean isResource() {
return (!isFolder);
}
/**
* Sets a new StoredObject as a collection or resource
*
* @param f
* true - collection ; false - resource
*/
public void setFolder(boolean f) {
this.isFolder = f;
}
/**
* Gets the date of the last modification
*
* @return last modification Date
*/
public Date getLastModified() {
return (lastModified);
}
/**
* Sets the date of the last modification
*
* @param d
* date of the last modification
*/
public void setLastModified(Date d) {
this.lastModified = d;
}
/**
* Gets the date of the creation
*
* @return creation Date
*/
public Date getCreationDate() {
return (creationDate);
}
/**
* Sets the date of the creation
*
* @param d
* date of the creation
*/
public void setCreationDate(Date c) {
this.creationDate = c;
}
/**
* Gets the length of the resource content
*
* @return length of the resource content
*/
public long getResourceLength() {
return (contentLength);
}
/**
* Sets the length of the resource content
*
* @param l
* the length of the resource content
*/
public void setResourceLength(long l) {
this.contentLength = l;
}
/**
* Gets the state of the resource
*
* @return true if the resource is in lock-null state
*/
public boolean isNullResource() {
return isNullRessource;
}
/**
* Sets a StoredObject as a lock-null resource
*
* @param f
* true to set the resource as lock-null resource
*/
public void setNullResource(boolean f) {
this.isNullRessource = f;
this.isFolder = false;
this.creationDate = null;
this.lastModified = null;
// this.content = null;
this.contentLength = 0;
this.mimeType= null;
}
/**
* Retrieve the myme type from the store object.
* Can also return NULL if the store does not handle
* mime type stuff.
* In that case the mime type is determined by the servletcontext
*
* @return the mimeType
*/
public String getMimeType() {
return mimeType;
}
/**
* Set the mime type of this object
*
* @param mimeType the mimeType to set
*/
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
}

View File

@ -0,0 +1,219 @@
package net.sf.webdav;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.HashMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.exceptions.UnauthenticatedException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.fromcatalina.MD5Encoder;
import net.sf.webdav.locking.ResourceLocks;
import net.sf.webdav.methods.DoCopy;
import net.sf.webdav.methods.DoDelete;
import net.sf.webdav.methods.DoGet;
import net.sf.webdav.methods.DoHead;
import net.sf.webdav.methods.DoLock;
import net.sf.webdav.methods.DoMkcol;
import net.sf.webdav.methods.DoMove;
import net.sf.webdav.methods.DoNotImplemented;
import net.sf.webdav.methods.DoOptions;
import net.sf.webdav.methods.DoPropfind;
import net.sf.webdav.methods.DoProppatch;
import net.sf.webdav.methods.DoPut;
import net.sf.webdav.methods.DoUnlock;
public class WebDavServletBean extends HttpServlet {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(WebDavServletBean.class);
/**
* MD5 message digest provider.
*/
protected static MessageDigest MD5_HELPER;
/**
* The MD5 helper object for this class.
*/
protected static final MD5Encoder MD5_ENCODER = new MD5Encoder();
private static final boolean READ_ONLY = false;
protected ResourceLocks _resLocks;
protected IWebdavStore _store;
private HashMap<String, IMethodExecutor> _methodMap = new HashMap<String, IMethodExecutor>();
public WebDavServletBean() {
_resLocks = new ResourceLocks();
try {
MD5_HELPER = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException();
}
}
public void init(IWebdavStore store, String dftIndexFile,
String insteadOf404, int nocontentLenghHeaders,
boolean lazyFolderCreationOnPut) throws ServletException {
_store = store;
IMimeTyper mimeTyper = new IMimeTyper() {
public String getMimeType(ITransaction transaction, String path) {
String retVal= _store.getStoredObject(transaction, path).getMimeType();
if ( retVal== null) {
retVal= getServletContext().getMimeType( path);
}
return retVal;
}
};
register("GET", new DoGet(store, dftIndexFile, insteadOf404, _resLocks,
mimeTyper, nocontentLenghHeaders));
register("HEAD", new DoHead(store, dftIndexFile, insteadOf404,
_resLocks, mimeTyper, nocontentLenghHeaders));
DoDelete doDelete = (DoDelete) register("DELETE", new DoDelete(store,
_resLocks, READ_ONLY));
DoCopy doCopy = (DoCopy) register("COPY", new DoCopy(store, _resLocks,
doDelete, READ_ONLY));
register("LOCK", new DoLock(store, _resLocks, READ_ONLY));
register("UNLOCK", new DoUnlock(store, _resLocks, READ_ONLY));
register("MOVE", new DoMove(store, _resLocks, doDelete, doCopy, READ_ONLY));
register("MKCOL", new DoMkcol(store, _resLocks, READ_ONLY));
register("OPTIONS", new DoOptions(store, _resLocks));
register("PUT", new DoPut(store, _resLocks, READ_ONLY,
lazyFolderCreationOnPut));
register("PROPFIND", new DoPropfind(store, _resLocks, mimeTyper));
register("PROPPATCH", new DoProppatch(store, _resLocks, READ_ONLY));
register("*NO*IMPL*", new DoNotImplemented(READ_ONLY));
}
@Override
public void destroy() {
if(_store != null)
_store.destroy();
super.destroy();
}
protected IMethodExecutor register(String methodName, IMethodExecutor method) {
_methodMap.put(methodName, method);
return method;
}
/**
* Handles the special WebDAV methods.
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String methodName = req.getMethod();
ITransaction transaction = null;
boolean needRollback = false;
if (LOG.isTraceEnabled())
debugRequest(methodName, req);
try {
Principal userPrincipal = getUserPrincipal(req);
transaction = _store.begin(userPrincipal);
needRollback = true;
_store.checkAuthentication(transaction);
resp.setStatus(WebdavStatus.SC_OK);
try {
IMethodExecutor methodExecutor = (IMethodExecutor) _methodMap
.get(methodName);
if (methodExecutor == null) {
methodExecutor = (IMethodExecutor) _methodMap
.get("*NO*IMPL*");
}
methodExecutor.execute(transaction, req, resp);
_store.commit(transaction);
/** Clear not consumed data
*
* Clear input stream if available otherwise later access
* include current input. These cases occure if the client
* sends a request with body to an not existing resource.
*/
if (req.getContentLength() != 0 && req.getInputStream().available() > 0) {
if (LOG.isTraceEnabled()) { LOG.trace("Clear not consumed data!"); }
while (req.getInputStream().available() > 0) {
req.getInputStream().read();
}
}
needRollback = false;
} catch (IOException e) {
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
e.printStackTrace(pw);
LOG.error("IOException: " + sw.toString());
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
_store.rollback(transaction);
throw new ServletException(e);
}
} catch (UnauthenticatedException e) {
resp.sendError(e.getCode());
} catch (WebdavException e) {
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
e.printStackTrace(pw);
LOG.error("WebdavException: " + sw.toString());
throw new ServletException(e);
} catch (Exception e) {
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
e.printStackTrace(pw);
LOG.error("Exception: " + sw.toString());
} finally {
if (needRollback)
_store.rollback(transaction);
}
}
/**
* Method that permit to customize the way
* user information are extracted from the request, default use JAAS
* @param req
* @return
*/
protected Principal getUserPrincipal(HttpServletRequest req) {
return req.getUserPrincipal();
}
private void debugRequest(String methodName, HttpServletRequest req) {
LOG.trace("-----------");
LOG.trace("WebdavServlet\n request: methodName = " + methodName);
LOG.trace("time: " + System.currentTimeMillis());
LOG.trace("path: " + req.getRequestURI());
LOG.trace("-----------");
Enumeration<?> e = req.getHeaderNames();
while (e.hasMoreElements()) {
String s = (String) e.nextElement();
LOG.trace("header: " + s + " " + req.getHeader(s));
}
e = req.getAttributeNames();
while (e.hasMoreElements()) {
String s = (String) e.nextElement();
LOG.trace("attribute: " + s + " " + req.getAttribute(s));
}
e = req.getParameterNames();
while (e.hasMoreElements()) {
String s = (String) e.nextElement();
LOG.trace("parameter: " + s + " " + req.getParameter(s));
}
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav;
import java.io.File;
import java.lang.reflect.Constructor;
import javax.servlet.ServletException;
import net.sf.webdav.exceptions.WebdavException;
/**
* Servlet which provides support for WebDAV level 2.
*
* the original class is org.apache.catalina.servlets.WebdavServlet by Remy
* Maucherat, which was heavily changed
*
* @author Remy Maucherat
*/
public class WebdavServlet extends WebDavServletBean {
private static final String ROOTPATH_PARAMETER = "rootpath";
public void init() throws ServletException {
// Parameters from web.xml
String clazzName = getServletConfig().getInitParameter(
"ResourceHandlerImplementation");
if (clazzName == null || clazzName.equals("")) {
clazzName = LocalFileSystemStore.class.getName();
}
File root = getFileRoot();
IWebdavStore webdavStore = constructStore(clazzName, root);
boolean lazyFolderCreationOnPut = getInitParameter("lazyFolderCreationOnPut") != null
&& getInitParameter("lazyFolderCreationOnPut").equals("1");
String dftIndexFile = getInitParameter("default-index-file");
String insteadOf404 = getInitParameter("instead-of-404");
int noContentLengthHeader = getIntInitParameter("no-content-length-headers");
super.init(webdavStore, dftIndexFile, insteadOf404,
noContentLengthHeader, lazyFolderCreationOnPut);
}
private int getIntInitParameter(String key) {
return getInitParameter(key) == null ? -1 : Integer
.parseInt(getInitParameter(key));
}
protected IWebdavStore constructStore(String clazzName, File root) {
IWebdavStore webdavStore;
try {
Class<?> clazz = WebdavServlet.class.getClassLoader().loadClass(
clazzName);
Constructor<?> ctor = clazz
.getConstructor(new Class[] { File.class });
webdavStore = (IWebdavStore) ctor
.newInstance(new Object[] { root });
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("some problem making store component", e);
}
return webdavStore;
}
private File getFileRoot() {
String rootPath = getInitParameter(ROOTPATH_PARAMETER);
if (rootPath == null) {
throw new WebdavException("missing parameter: "
+ ROOTPATH_PARAMETER);
}
if (rootPath.equals("*WAR-FILE-ROOT*")) {
String file = LocalFileSystemStore.class.getProtectionDomain()
.getCodeSource().getLocation().getFile().replace('\\', '/');
if (file.charAt(0) == '/'
&& System.getProperty("os.name").indexOf("Windows") != -1) {
file = file.substring(1, file.length());
}
int ix = file.indexOf("/WEB-INF/");
if (ix != -1) {
rootPath = file.substring(0, ix).replace('/',
File.separatorChar);
} else {
throw new WebdavException(
"Could not determine root of war file. Can't extract from path '"
+ file + "' for this web container");
}
}
return new File(rootPath);
}
}

View File

@ -0,0 +1,273 @@
package net.sf.webdav;
import java.util.Hashtable;
import javax.servlet.http.HttpServletResponse;
/**
* Wraps the HttpServletResponse class to abstract the specific protocol used.
* To support other protocols we would only need to modify this class and the
* WebDavRetCode classes.
*
* @author Marc Eaddy
* @version 1.0, 16 Nov 1997
*/
public class WebdavStatus {
// ----------------------------------------------------- Instance Variables
/**
* This Hashtable contains the mapping of HTTP and WebDAV status codes to
* descriptive text. This is a static variable.
*/
private static Hashtable<Integer, String> _mapStatusCodes = new Hashtable<Integer, String>();
// ------------------------------------------------------ HTTP Status Codes
/**
* Status code (200) indicating the request succeeded normally.
*/
public static final int SC_OK = HttpServletResponse.SC_OK;
/**
* Status code (201) indicating the request succeeded and created a new
* resource on the server.
*/
public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
/**
* Status code (202) indicating that a request was accepted for processing,
* but was not completed.
*/
public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
/**
* Status code (204) indicating that the request succeeded but that there
* was no new information to return.
*/
public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
/**
* Status code (301) indicating that the resource has permanently moved to a
* new location, and that future references should use a new URI with their
* requests.
*/
public static final int SC_MOVED_PERMANENTLY = HttpServletResponse.SC_MOVED_PERMANENTLY;
/**
* Status code (302) indicating that the resource has temporarily moved to
* another location, but that future references should still use the
* original URI to access the resource.
*/
public static final int SC_MOVED_TEMPORARILY = HttpServletResponse.SC_MOVED_TEMPORARILY;
/**
* Status code (304) indicating that a conditional GET operation found that
* the resource was available and not modified.
*/
public static final int SC_NOT_MODIFIED = HttpServletResponse.SC_NOT_MODIFIED;
/**
* Status code (400) indicating the request sent by the client was
* syntactically incorrect.
*/
public static final int SC_BAD_REQUEST = HttpServletResponse.SC_BAD_REQUEST;
/**
* Status code (401) indicating that the request requires HTTP
* authentication.
*/
public static final int SC_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
/**
* Status code (403) indicating the server understood the request but
* refused to fulfill it.
*/
public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
/**
* Status code (404) indicating that the requested resource is not
* available.
*/
public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
/**
* Status code (500) indicating an error inside the HTTP service which
* prevented it from fulfilling the request.
*/
public static final int SC_INTERNAL_SERVER_ERROR = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
/**
* Status code (501) indicating the HTTP service does not support the
* functionality needed to fulfill the request.
*/
public static final int SC_NOT_IMPLEMENTED = HttpServletResponse.SC_NOT_IMPLEMENTED;
/**
* Status code (502) indicating that the HTTP server received an invalid
* response from a server it consulted when acting as a proxy or gateway.
*/
public static final int SC_BAD_GATEWAY = HttpServletResponse.SC_BAD_GATEWAY;
/**
* Status code (503) indicating that the HTTP service is temporarily
* overloaded, and unable to handle the request.
*/
public static final int SC_SERVICE_UNAVAILABLE = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
/**
* Status code (100) indicating the client may continue with its request.
* This interim response is used to inform the client that the initial part
* of the request has been received and has not yet been rejected by the
* server.
*/
public static final int SC_CONTINUE = 100;
/**
* Status code (405) indicating the method specified is not allowed for the
* resource.
*/
public static final int SC_METHOD_NOT_ALLOWED = 405;
/**
* Status code (409) indicating that the request could not be completed due
* to a conflict with the current state of the resource.
*/
public static final int SC_CONFLICT = 409;
/**
* Status code (412) indicating the precondition given in one or more of the
* request-header fields evaluated to false when it was tested on the
* server.
*/
public static final int SC_PRECONDITION_FAILED = 412;
/**
* Status code (413) indicating the server is refusing to process a request
* because the request entity is larger than the server is willing or able
* to process.
*/
public static final int SC_REQUEST_TOO_LONG = 413;
/**
* Status code (415) indicating the server is refusing to service the
* request because the entity of the request is in a format not supported by
* the requested resource for the requested method.
*/
public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
// -------------------------------------------- Extended WebDav status code
/**
* Status code (207) indicating that the response requires providing status
* for multiple independent operations.
*/
public static final int SC_MULTI_STATUS = 207;
// This one colides with HTTP 1.1
// "207 Parital Update OK"
/**
* Status code (418) indicating the entity body submitted with the PATCH
* method was not understood by the resource.
*/
public static final int SC_UNPROCESSABLE_ENTITY = 418;
// This one colides with HTTP 1.1
// "418 Reauthentication Required"
/**
* Status code (419) indicating that the resource does not have sufficient
* space to record the state of the resource after the execution of this
* method.
*/
public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
// This one colides with HTTP 1.1
// "419 Proxy Reauthentication Required"
/**
* Status code (420) indicating the method was not executed on a particular
* resource within its scope because some part of the method's execution
* failed causing the entire method to be aborted.
*/
public static final int SC_METHOD_FAILURE = 420;
/**
* Status code (423) indicating the destination resource of a method is
* locked, and either the request did not contain a valid Lock-Info header,
* or the Lock-Info header identifies a lock held by another principal.
*/
public static final int SC_LOCKED = 423;
// ------------------------------------------------------------ Initializer
static {
// HTTP 1.0 Status Code
addStatusCodeMap(SC_OK, "OK");
addStatusCodeMap(SC_CREATED, "Created");
addStatusCodeMap(SC_ACCEPTED, "Accepted");
addStatusCodeMap(SC_NO_CONTENT, "No Content");
addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
addStatusCodeMap(SC_NOT_FOUND, "Not Found");
addStatusCodeMap(SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
addStatusCodeMap(SC_CONTINUE, "Continue");
addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
addStatusCodeMap(SC_CONFLICT, "Conflict");
addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type");
// WebDav Status Codes
addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
addStatusCodeMap(SC_UNPROCESSABLE_ENTITY, "Unprocessable Entity");
addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE,
"Insufficient Space On Resource");
addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
addStatusCodeMap(SC_LOCKED, "Locked");
}
// --------------------------------------------------------- Public Methods
/**
* Returns the HTTP status text for the HTTP or WebDav status code specified
* by looking it up in the static mapping. This is a static function.
*
* @param nHttpStatusCode
* [IN] HTTP or WebDAV status code
* @return A string with a short descriptive phrase for the HTTP status code
* (e.g., "OK").
*/
public static String getStatusText(int nHttpStatusCode) {
Integer intKey = new Integer(nHttpStatusCode);
if (!_mapStatusCodes.containsKey(intKey)) {
return "";
} else {
return (String) _mapStatusCodes.get(intKey);
}
}
// -------------------------------------------------------- Private Methods
/**
* Adds a new status code -> status text mapping. This is a static method
* because the mapping is a static variable.
*
* @param nKey
* [IN] HTTP or WebDAV status code
* @param strVal
* [IN] HTTP status text
*/
private static void addStatusCodeMap(int nKey, String strVal) {
_mapStatusCodes.put(new Integer(nKey), strVal);
}
};

View File

@ -0,0 +1,36 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.exceptions;
public class AccessDeniedException extends WebdavException {
public AccessDeniedException() {
super();
}
public AccessDeniedException(String message) {
super(message);
}
public AccessDeniedException(String message, Throwable cause) {
super(message, cause);
}
public AccessDeniedException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,20 @@
package net.sf.webdav.exceptions;
public class LockFailedException extends WebdavException {
public LockFailedException() {
super();
}
public LockFailedException(String message) {
super(message);
}
public LockFailedException(String message, Throwable cause) {
super(message, cause);
}
public LockFailedException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.exceptions;
public class ObjectAlreadyExistsException extends WebdavException {
public ObjectAlreadyExistsException() {
super();
}
public ObjectAlreadyExistsException(String message) {
super(message);
}
public ObjectAlreadyExistsException(String message, Throwable cause) {
super(message, cause);
}
public ObjectAlreadyExistsException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.exceptions;
public class ObjectNotFoundException extends WebdavException {
public ObjectNotFoundException() {
super();
}
public ObjectNotFoundException(String message) {
super(message);
}
public ObjectNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public ObjectNotFoundException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.exceptions;
public class UnauthenticatedException extends WebdavException {
private final int code;
public UnauthenticatedException(int code) {
super();
this.code = code;
}
public int getCode() {
return code;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.exceptions;
public class WebdavException extends RuntimeException {
public WebdavException() {
super();
}
public WebdavException(String message) {
super(message);
}
public WebdavException(String message, Throwable cause) {
super(message, cause);
}
public WebdavException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.fromcatalina;
/**
* Encode an MD5 digest into a String.
* <p>
* The 128 bit MD5 hash is converted into a 32 character long String. Each
* character of the String is the hexadecimal representation of 4 bits of the
* digest.
*
* @author Remy Maucherat
* @version $Revision: 1.2 $ $Date: 2008-08-05 07:38:45 $
*/
public final class MD5Encoder {
// ----------------------------------------------------- Instance Variables
private static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
// --------------------------------------------------------- Public Methods
/**
* Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
*
* @param binaryData
* Array containing the digest
* @return Encoded MD5, or null if encoding failed
*/
public String encode(byte[] binaryData) {
if (binaryData.length != 16)
return null;
char[] buffer = new char[32];
for (int i = 0; i < 16; i++) {
int low = (int) (binaryData[i] & 0x0f);
int high = (int) ((binaryData[i] & 0xf0) >> 4);
buffer[i * 2] = HEXADECIMAL[high];
buffer[i * 2 + 1] = HEXADECIMAL[low];
}
return new String(buffer);
}
}

View File

@ -0,0 +1,510 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.fromcatalina;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.http.Cookie;
/**
* General purpose request parsing and encoding utility methods.
*
* @author Craig R. McClanahan
* @author Tim Tye
* @version $Revision: 1.2 $ $Date: 2008-08-05 07:38:45 $
*/
public final class RequestUtil {
/**
* Encode a cookie as per RFC 2109. The resulting string can be used as the
* value for a <code>Set-Cookie</code> header.
*
* @param cookie
* The cookie to encode.
* @return A string following RFC 2109.
*/
public static String encodeCookie(Cookie cookie) {
StringBuffer buf = new StringBuffer(cookie.getName());
buf.append("=");
buf.append(cookie.getValue());
String comment = cookie.getComment();
if (comment != null) {
buf.append("; Comment=\"");
buf.append(comment);
buf.append("\"");
}
String domain = cookie.getDomain();
if (domain != null) {
buf.append("; Domain=\"");
buf.append(domain);
buf.append("\"");
}
int age = cookie.getMaxAge();
if (age >= 0) {
buf.append("; Max-Age=\"");
buf.append(age);
buf.append("\"");
}
String path = cookie.getPath();
if (path != null) {
buf.append("; Path=\"");
buf.append(path);
buf.append("\"");
}
if (cookie.getSecure()) {
buf.append("; Secure");
}
int version = cookie.getVersion();
if (version > 0) {
buf.append("; Version=\"");
buf.append(version);
buf.append("\"");
}
return (buf.toString());
}
/**
* Filter the specified message string for characters that are sensitive in
* HTML. This avoids potential attacks caused by including JavaScript codes
* in the request URL that is often reported in error messages.
*
* @param message
* The message string to be filtered
*/
public static String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("&lt;");
break;
case '>':
result.append("&gt;");
break;
case '&':
result.append("&amp;");
break;
case '"':
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
/**
* Normalize a relative URI path that may have relative values ("/./",
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
* useful only for normalizing application-generated paths. It does not try
* to perform security checks for malicious input.
*
* @param path
* Relative path to be normalized
*/
public static String normalize(String path) {
if (path == null)
return null;
// Create a place for the normalized path
String normalized = path;
if (normalized.equals("/."))
return "/";
// Add a leading "/" if necessary
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index)
+ normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index)
+ normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2)
+ normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
/**
* Parse the character encoding from the specified content type header. If
* the content type is null, or there is no explicit character encoding,
* <code>null</code> is returned.
*
* @param contentType
* a content type header
*/
public static String parseCharacterEncoding(String contentType) {
if (contentType == null)
return (null);
int start = contentType.indexOf("charset=");
if (start < 0)
return (null);
String encoding = contentType.substring(start + 8);
int end = encoding.indexOf(';');
if (end >= 0)
encoding = encoding.substring(0, end);
encoding = encoding.trim();
if ((encoding.length() > 2) && (encoding.startsWith("\""))
&& (encoding.endsWith("\"")))
encoding = encoding.substring(1, encoding.length() - 1);
return (encoding.trim());
}
/**
* Parse a cookie header into an array of cookies according to RFC 2109.
*
* @param header
* Value of an HTTP "Cookie" header
*/
public static Cookie[] parseCookieHeader(String header) {
if ((header == null) || (header.length() < 1))
return (new Cookie[0]);
ArrayList<Cookie> cookies = new ArrayList<Cookie>();
while (header.length() > 0) {
int semicolon = header.indexOf(';');
if (semicolon < 0)
semicolon = header.length();
if (semicolon == 0)
break;
String token = header.substring(0, semicolon);
if (semicolon < header.length())
header = header.substring(semicolon + 1);
else
header = "";
try {
int equals = token.indexOf('=');
if (equals > 0) {
String name = token.substring(0, equals).trim();
String value = token.substring(equals + 1).trim();
cookies.add(new Cookie(name, value));
}
} catch (Throwable e) {
;
}
}
return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
}
/**
* Append request parameters from the specified String to the specified Map.
* It is presumed that the specified Map is not accessed from any other
* thread, so no synchronization is performed.
* <p>
* <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
* individually on the parsed name and value elements, rather than on the
* entire query string ahead of time, to properly deal with the case where
* the name or value includes an encoded "=" or "&" character that would
* otherwise be interpreted as a delimiter.
*
* @param map
* Map that accumulates the resulting parameters
* @param data
* Input string containing request parameters
*
* @exception IllegalArgumentException
* if the data is malformed
*/
public static void parseParameters(Map<String, String[]> map, String data,
String encoding) throws UnsupportedEncodingException {
if ((data != null) && (data.length() > 0)) {
// use the specified encoding to extract bytes out of the
// given string so that the encoding is not lost. If an
// encoding is not specified, let it use platform default
byte[] bytes = null;
try {
if (encoding == null) {
bytes = data.getBytes();
} else {
bytes = data.getBytes(encoding);
}
} catch (UnsupportedEncodingException uee) {
}
parseParameters(map, bytes, encoding);
}
}
/**
* Decode and return the specified URL-encoded String. When the byte array
* is converted to a string, the system default character encoding is
* used... This may be different than some other servers.
*
* @param str
* The url-encoded string
*
* @exception IllegalArgumentException
* if a '%' character is not followed by a valid 2-digit hexadecimal
* number
*/
public static String URLDecode(String str) {
return URLDecode(str, null);
}
/**
* Decode and return the specified URL-encoded String.
*
* @param str
* The url-encoded string
* @param enc
* The encoding to use; if null, the default encoding is used
* @exception IllegalArgumentException
* if a '%' character is not followed by a valid 2-digit hexadecimal
* number
*/
public static String URLDecode(String str, String enc) {
if (str == null)
return (null);
// use the specified encoding to extract bytes out of the
// given string so that the encoding is not lost. If an
// encoding is not specified, let it use platform default
byte[] bytes = null;
try {
if (enc == null) {
bytes = str.getBytes();
} else {
bytes = str.getBytes(enc);
}
} catch (UnsupportedEncodingException uee) {
}
return URLDecode(bytes, enc);
}
/**
* Decode and return the specified URL-encoded byte array.
*
* @param bytes
* The url-encoded byte array
* @exception IllegalArgumentException
* if a '%' character is not followed by a valid 2-digit hexadecimal
* number
*/
public static String URLDecode(byte[] bytes) {
return URLDecode(bytes, null);
}
/**
* Decode and return the specified URL-encoded byte array.
*
* @param bytes
* The url-encoded byte array
* @param enc
* The encoding to use; if null, the default encoding is used
* @exception IllegalArgumentException
* if a '%' character is not followed by a valid 2-digit hexadecimal
* number
*/
public static String URLDecode(byte[] bytes, String enc) {
if (bytes == null)
return (null);
int len = bytes.length;
int ix = 0;
int ox = 0;
while (ix < len) {
byte b = bytes[ix++]; // Get byte to test
if (b == '+') {
b = (byte) ' ';
} else if (b == '%') {
b = (byte) ((convertHexDigit(bytes[ix++]) << 4) + convertHexDigit(bytes[ix++]));
}
bytes[ox++] = b;
}
if (enc != null) {
try {
return new String(bytes, 0, ox, enc);
} catch (Exception e) {
e.printStackTrace();
}
}
return new String(bytes, 0, ox);
}
/**
* Convert a byte character value to hexidecimal digit value.
*
* @param b
* the character value byte
*/
private static byte convertHexDigit(byte b) {
if ((b >= '0') && (b <= '9'))
return (byte) (b - '0');
if ((b >= 'a') && (b <= 'f'))
return (byte) (b - 'a' + 10);
if ((b >= 'A') && (b <= 'F'))
return (byte) (b - 'A' + 10);
return 0;
}
/**
* Put name and value pair in map. When name already exist, add value to
* array of values.
*
* @param map
* The map to populate
* @param name
* The parameter name
* @param value
* The parameter value
*/
private static void putMapEntry(Map<String, String[]> map, String name,
String value) {
String[] newValues = null;
String[] oldValues = (String[]) map.get(name);
if (oldValues == null) {
newValues = new String[1];
newValues[0] = value;
} else {
newValues = new String[oldValues.length + 1];
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
newValues[oldValues.length] = value;
}
map.put(name, newValues);
}
/**
* Append request parameters from the specified String to the specified Map.
* It is presumed that the specified Map is not accessed from any other
* thread, so no synchronization is performed.
* <p>
* <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
* individually on the parsed name and value elements, rather than on the
* entire query string ahead of time, to properly deal with the case where
* the name or value includes an encoded "=" or "&" character that would
* otherwise be interpreted as a delimiter. NOTE: byte array data is
* modified by this method. Caller beware.
*
* @param map
* Map that accumulates the resulting parameters
* @param data
* Input string containing request parameters
* @param encoding
* Encoding to use for converting hex
*
* @exception UnsupportedEncodingException
* if the data is malformed
*/
public static void parseParameters(Map<String, String[]> map, byte[] data,
String encoding) throws UnsupportedEncodingException {
if (data != null && data.length > 0) {
int ix = 0;
int ox = 0;
String key = null;
String value = null;
while (ix < data.length) {
byte c = data[ix++];
switch ((char) c) {
case '&':
value = new String(data, 0, ox, encoding);
if (key != null) {
putMapEntry(map, key, value);
key = null;
}
ox = 0;
break;
case '=':
if (key == null) {
key = new String(data, 0, ox, encoding);
ox = 0;
} else {
data[ox++] = c;
}
break;
case '+':
data[ox++] = (byte) ' ';
break;
case '%':
data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4) + convertHexDigit(data[ix++]));
break;
default:
data[ox++] = c;
}
}
// The last value does not end in '&'. So save it now.
if (key != null) {
value = new String(data, 0, ox, encoding);
putMapEntry(map, key, value);
}
}
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.fromcatalina;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.BitSet;
/**
*
* This class is very similar to the java.net.URLEncoder class.
*
* Unfortunately, with java.net.URLEncoder there is no way to specify to the
* java.net.URLEncoder which characters should NOT be encoded.
*
* This code was moved from DefaultServlet.java
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public class URLEncoder
{
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(URLEncoder.class);
protected static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
// Array containing the safe characters set.
protected BitSet _safeCharacters = new BitSet(256);
public URLEncoder() {
for (char i = 'a'; i <= 'z'; i++) {
addSafeCharacter(i);
}
for (char i = 'A'; i <= 'Z'; i++) {
addSafeCharacter(i);
}
for (char i = '0'; i <= '9'; i++) {
addSafeCharacter(i);
}
for(char c : "$-_.+!*'(),".toCharArray()){
addSafeCharacter(c);
}
}
public void addSafeCharacter(char c) {
_safeCharacters.set(c);
}
public String encode(String path) {
int maxBytesPerChar = 10;
// int caseDiff = ('a' - 'A');
StringBuffer rewrittenPath = new StringBuffer(path.length());
ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
OutputStreamWriter writer = null;
try {
writer = new OutputStreamWriter(buf, "UTF8");
} catch (Exception e) {
LOG.error("Error in encode <"+path+">", e);
writer = new OutputStreamWriter(buf);
}
for (int i = 0; i < path.length(); i++) {
int c = (int) path.charAt(i);
if (_safeCharacters.get(c)) {
rewrittenPath.append((char) c);
} else {
// convert to external encoding before hex conversion
try {
writer.write((char) c);
writer.flush();
} catch (IOException e) {
buf.reset();
continue;
}
byte[] ba = buf.toByteArray();
for (int j = 0; j < ba.length; j++) {
// Converting each byte in the buffer
byte toEncode = ba[j];
rewrittenPath.append('%');
int low = (int) (toEncode & 0x0f);
int high = (int) ((toEncode & 0xf0) >> 4);
rewrittenPath.append(HEXADECIMAL[high]);
rewrittenPath.append(HEXADECIMAL[low]);
}
buf.reset();
}
}
return rewrittenPath.toString();
}
}

View File

@ -0,0 +1,42 @@
package net.sf.webdav.fromcatalina;
import java.util.Vector;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class XMLHelper {
public static Node findSubElement(Node parent, String localName) {
if (parent == null) {
return null;
}
Node child = parent.getFirstChild();
while (child != null) {
if ((child.getNodeType() == Node.ELEMENT_NODE)
&& (child.getLocalName().equals(localName))) {
return child;
}
child = child.getNextSibling();
}
return null;
}
public static Vector<String> getPropertiesFromXML(Node propNode) {
Vector<String> properties;
properties = new Vector<String>();
NodeList childList = propNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
String nodeName = currentNode.getLocalName();
String namespace = currentNode.getNamespaceURI();
// href is a live property which is handled differently
properties.addElement(namespace + ":" + nodeName);
}
}
return properties;
}
}

View File

@ -0,0 +1,214 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.fromcatalina;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
/**
* XMLWriter helper class.
*
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
*/
public class XMLWriter {
// -------------------------------------------------------------- Constants
/**
* Opening tag.
*/
public static final int OPENING = 0;
/**
* Closing tag.
*/
public static final int CLOSING = 1;
/**
* Element with no content.
*/
public static final int NO_CONTENT = 2;
// ----------------------------------------------------- Instance Variables
/**
* Buffer.
*/
protected StringBuffer _buffer = new StringBuffer();
/**
* Writer.
*/
protected Writer _writer = null;
/**
* Namespaces to be declared in the root element
*/
protected Map<String, String> _namespaces;
/**
* Is true until the root element is written
*/
protected boolean _isRootElement = true;
// ----------------------------------------------------------- Constructors
/**
* Constructor.
*/
public XMLWriter(Map<String, String> namespaces) {
_namespaces = namespaces;
}
/**
* Constructor.
*/
public XMLWriter(Writer writer, Map<String, String> namespaces) {
_writer = writer;
_namespaces = namespaces;
}
// --------------------------------------------------------- Public Methods
/**
* Retrieve generated XML.
*
* @return String containing the generated XML
*/
public String toString() {
return _buffer.toString();
}
/**
* Write property to the XML.
*
* @param name
* Property name
* @param value
* Property value
*/
public void writeProperty(String name, String value) {
writeElement(name, OPENING);
_buffer.append(value);
writeElement(name, CLOSING);
}
/**
* Write property to the XML.
*
* @param name
* Property name
*/
public void writeProperty(String name) {
writeElement(name, NO_CONTENT);
}
/**
* Write an element.
*
* @param name
* Element name
* @param type
* Element type
*/
public void writeElement(String name, int type) {
StringBuffer nsdecl = new StringBuffer();
if (_isRootElement) {
for (Iterator<String> iter = _namespaces.keySet().iterator(); iter
.hasNext();) {
String fullName = (String) iter.next();
String abbrev = (String) _namespaces.get(fullName);
nsdecl.append(" xmlns:").append(abbrev).append("=\"").append(
fullName).append("\"");
}
_isRootElement = false;
}
int pos = name.lastIndexOf(':');
if (pos >= 0) {
// lookup prefix for namespace
String fullns = name.substring(0, pos);
String prefix = (String) _namespaces.get(fullns);
if (prefix == null) {
// there is no prefix for this namespace
name = name.substring(pos + 1);
nsdecl.append(" xmlns=\"").append(fullns).append("\"");
} else {
// there is a prefix
name = prefix + ":" + name.substring(pos + 1);
}
} else {
throw new IllegalArgumentException(
"All XML elements must have a namespace");
}
switch (type) {
case OPENING:
_buffer.append("<" + name + nsdecl + ">");
break;
case CLOSING:
_buffer.append("</" + name + ">\n");
break;
case NO_CONTENT:
default:
_buffer.append("<" + name + nsdecl + "/>");
break;
}
}
/**
* Write text.
*
* @param text
* Text to append
*/
public void writeText(String text) {
_buffer.append(text);
}
/**
* Write data.
*
* @param data
* Data to append
*/
public void writeData(String data) {
_buffer.append("<![CDATA[" + data + "]]>");
}
/**
* Write XML Header.
*/
public void writeXMLHeader() {
_buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
}
/**
* Send data and reinitializes buffer.
*/
public void sendData() throws IOException {
if (_writer != null) {
_writer.write(_buffer.toString());
_writer.flush();
_buffer = new StringBuffer();
}
}
}

View File

@ -0,0 +1,144 @@
package net.sf.webdav.locking;
import net.sf.webdav.ITransaction;
import net.sf.webdav.exceptions.LockFailedException;
public interface IResourceLocks {
/**
* Tries to lock the resource at "path".
*
* @param transaction
* @param path
* what resource to lock
* @param owner
* the owner of the lock
* @param exclusive
* if the lock should be exclusive (or shared)
* @param depth
* depth
* @param timeout
* Lock Duration in seconds.
* @return true if the resource at path was successfully locked, false if an
* existing lock prevented this
* @throws LockFailedException
*/
boolean lock(ITransaction transaction, String path, String owner,
boolean exclusive, int depth, int timeout, boolean temporary)
throws LockFailedException;
/**
* Unlocks all resources at "path" (and all subfolders if existing)<p/> that
* have the same owner.
*
* @param transaction
* @param id
* id to the resource to unlock
* @param owner
* who wants to unlock
*/
boolean unlock(ITransaction transaction, String id, String owner);
/**
* Unlocks all resources at "path" (and all subfolders if existing)<p/> that
* have the same owner.
*
* @param transaction
* @param path
* what resource to unlock
* @param owner
* who wants to unlock
*/
void unlockTemporaryLockedObjects(ITransaction transaction, String path,
String owner);
/**
* Deletes LockedObjects, where timeout has reached.
*
* @param transaction
* @param temporary
* Check timeout on temporary or real locks
*/
void checkTimeouts(ITransaction transaction, boolean temporary);
/**
* Tries to lock the resource at "path" exclusively.
*
* @param transaction
* Transaction
* @param path
* what resource to lock
* @param owner
* the owner of the lock
* @param depth
* depth
* @param timeout
* Lock Duration in seconds.
* @return true if the resource at path was successfully locked, false if an
* existing lock prevented this
* @throws LockFailedException
*/
boolean exclusiveLock(ITransaction transaction, String path, String owner,
int depth, int timeout) throws LockFailedException;
/**
* Tries to lock the resource at "path" shared.
*
* @param transaction
* Transaction
* @param path
* what resource to lock
* @param owner
* the owner of the lock
* @param depth
* depth
* @param timeout
* Lock Duration in seconds.
* @return true if the resource at path was successfully locked, false if an
* existing lock prevented this
* @throws LockFailedException
*/
boolean sharedLock(ITransaction transaction, String path, String owner,
int depth, int timeout) throws LockFailedException;
/**
* Gets the LockedObject corresponding to specified id.
*
* @param transaction
* @param id
* LockToken to requested resource
* @return LockedObject or null if no LockedObject on specified path exists
*/
LockedObject getLockedObjectByID(ITransaction transaction, String id);
/**
* Gets the LockedObject on specified path.
*
* @param transaction
* @param path
* Path to requested resource
* @return LockedObject or null if no LockedObject on specified path exists
*/
LockedObject getLockedObjectByPath(ITransaction transaction, String path);
/**
* Gets the LockedObject corresponding to specified id (locktoken).
*
* @param transaction
* @param id
* LockToken to requested resource
* @return LockedObject or null if no LockedObject on specified path exists
*/
LockedObject getTempLockedObjectByID(ITransaction transaction, String id);
/**
* Gets the LockedObject on specified path.
*
* @param transaction
* @param path
* Path to requested resource
* @return LockedObject or null if no LockedObject on specified path exists
*/
LockedObject getTempLockedObjectByPath(ITransaction transaction, String path);
}

View File

@ -0,0 +1,422 @@
package net.sf.webdav.locking;
import java.util.UUID;
/**
* a helper class for ResourceLocks, represents the Locks
*
* @author re
*
*/
public class LockedObject {
private ResourceLocks _resourceLocks;
private String _path;
private String _id;
/**
* Describing the depth of a locked collection. If the locked resource is
* not a collection, depth is 0 / doesn't matter.
*/
protected int _lockDepth;
/**
* Describing the timeout of a locked object (ms)
*/
protected long _expiresAt;
/**
* owner of the lock. shared locks can have multiple owners. is null if no
* owner is present
*/
// protected String[] _owner = null;
protected String[] _owner = null;
/**
* children of that lock
*/
protected LockedObject[] _children = null;
protected LockedObject _parent = null;
/**
* weather the lock is exclusive or not. if owner=null the exclusive value
* doesn't matter
*/
protected boolean _exclusive = false;
/**
* weather the lock is a write or read lock
*/
protected String _type = null;
/**
* @param _resourceLocks
* the resourceLocks where locks are stored
* @param path
* the path to the locked object
* @param temporary
* indicates if the LockedObject should be temporary or not
*/
public LockedObject(ResourceLocks resLocks, String path, boolean temporary) {
_path = path;
_id = UUID.randomUUID().toString();
_resourceLocks = resLocks;
if (!temporary) {
_resourceLocks._locks.put(path, this);
_resourceLocks._locksByID.put(_id, this);
} else {
_resourceLocks._tempLocks.put(path, this);
_resourceLocks._tempLocksByID.put(_id, this);
}
_resourceLocks._cleanupCounter++;
}
/**
* adds a new owner to a lock
*
* @param owner
* string that represents the owner
* @return true if the owner was added, false otherwise
*/
public boolean addLockedObjectOwner(String owner) {
if (_owner == null) {
_owner = new String[1];
} else {
int size = _owner.length;
String[] newLockObjectOwner = new String[size + 1];
// check if the owner is already here (that should actually not
// happen)
for (int i = 0; i < size; i++) {
if (_owner[i].equals(owner)) {
return false;
}
}
System.arraycopy(_owner, 0, newLockObjectOwner, 0, size);
_owner = newLockObjectOwner;
}
_owner[_owner.length - 1] = owner;
return true;
}
/**
* tries to remove the owner from the lock
*
* @param owner
* string that represents the owner
*/
public void removeLockedObjectOwner(String owner) {
try {
if (_owner != null) {
int size = _owner.length;
for (int i = 0; i < size; i++) {
// check every owner if it is the requested one
if (_owner[i].equals(owner)) {
// remove the owner
size -= 1;
String[] newLockedObjectOwner = new String[size];
for (int j = 0; j < size; j++) {
if (j < i) {
newLockedObjectOwner[j] = _owner[j];
} else {
newLockedObjectOwner[j] = _owner[j + 1];
}
}
_owner = newLockedObjectOwner;
}
}
if (_owner.length == 0) {
_owner = null;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("LockedObject.removeLockedObjectOwner()");
System.out.println(e.toString());
}
}
/**
* adds a new child lock to this lock
*
* @param newChild
* new child
*/
public void addChild(LockedObject newChild) {
if (_children == null) {
_children = new LockedObject[0];
}
int size = _children.length;
LockedObject[] newChildren = new LockedObject[size + 1];
System.arraycopy(_children, 0, newChildren, 0, size);
newChildren[size] = newChild;
_children = newChildren;
}
/**
* deletes this Lock object. assumes that it has no children and no owners
* (does not check this itself)
*
*/
public void removeLockedObject() {
if (this != _resourceLocks._root && !this.getPath().equals("/")) {
int size = _parent._children.length;
for (int i = 0; i < size; i++) {
if (_parent._children[i].equals(this)) {
LockedObject[] newChildren = new LockedObject[size - 1];
for (int i2 = 0; i2 < (size - 1); i2++) {
if (i2 < i) {
newChildren[i2] = _parent._children[i2];
} else {
newChildren[i2] = _parent._children[i2 + 1];
}
}
if (newChildren.length != 0) {
_parent._children = newChildren;
} else {
_parent._children = null;
}
break;
}
}
// removing from hashtable
_resourceLocks._locksByID.remove(getID());
_resourceLocks._locks.remove(getPath());
// now the garbage collector has some work to do
}
}
/**
* deletes this Lock object. assumes that it has no children and no owners
* (does not check this itself)
*
*/
public void removeTempLockedObject() {
if (this != _resourceLocks._tempRoot) {
// removing from tree
if (_parent != null && _parent._children != null) {
int size = _parent._children.length;
for (int i = 0; i < size; i++) {
if (_parent._children[i].equals(this)) {
LockedObject[] newChildren = new LockedObject[size - 1];
for (int i2 = 0; i2 < (size - 1); i2++) {
if (i2 < i) {
newChildren[i2] = _parent._children[i2];
} else {
newChildren[i2] = _parent._children[i2 + 1];
}
}
if (newChildren.length != 0) {
_parent._children = newChildren;
} else {
_parent._children = null;
}
break;
}
}
// removing from hashtable
_resourceLocks._tempLocksByID.remove(getID());
_resourceLocks._tempLocks.remove(getPath());
// now the garbage collector has some work to do
}
}
}
/**
* checks if a lock of the given exclusivity can be placed, only considering
* children up to "depth"
*
* @param exclusive
* wheather the new lock should be exclusive
* @param depth
* the depth to which should be checked
* @return true if the lock can be placed
*/
public boolean checkLocks(boolean exclusive, int depth) {
if (checkParents(exclusive) && checkChildren(exclusive, depth)) {
return true;
}
return false;
}
/**
* helper of checkLocks(). looks if the parents are locked
*
* @param exclusive
* wheather the new lock should be exclusive
* @return true if no locks at the parent path are forbidding a new lock
*/
private boolean checkParents(boolean exclusive) {
if (_path.equals("/")) {
return true;
} else {
if (_owner == null) {
// no owner, checking parents
return _parent != null && _parent.checkParents(exclusive);
} else {
// there already is a owner
return !(_exclusive || exclusive)
&& _parent.checkParents(exclusive);
}
}
}
/**
* helper of checkLocks(). looks if the children are locked
*
* @param exclusive
* wheather the new lock should be exclusive
* @return true if no locks at the children paths are forbidding a new lock
* @param depth
* depth
*/
private boolean checkChildren(boolean exclusive, int depth) {
if (_children == null) {
// a file
return _owner == null || !(_exclusive || exclusive);
} else {
// a folder
if (_owner == null) {
// no owner, checking children
if (depth != 0) {
boolean canLock = true;
int limit = _children.length;
for (int i = 0; i < limit; i++) {
if (!_children[i].checkChildren(exclusive, depth - 1)) {
canLock = false;
}
}
return canLock;
} else {
// depth == 0 -> we don't care for children
return true;
}
} else {
// there already is a owner
return !(_exclusive || exclusive);
}
}
}
/**
* Sets a new timeout for the LockedObject
*
* @param timeout
*/
public void refreshTimeout(int timeout) {
_expiresAt = System.currentTimeMillis() + (timeout * 1000);
}
/**
* Gets the timeout for the LockedObject
*
* @return timeout
*/
public long getTimeoutMillis() {
return (_expiresAt - System.currentTimeMillis());
}
/**
* Return true if the lock has expired.
*
* @return true if timeout has passed
*/
public boolean hasExpired() {
if (_expiresAt != 0) {
return (System.currentTimeMillis() > _expiresAt);
} else {
return true;
}
}
/**
* Gets the LockID (locktoken) for the LockedObject
*
* @return locktoken
*/
public String getID() {
return _id;
}
/**
* Gets the owners for the LockedObject
*
* @return owners
*/
public String[] getOwner() {
return _owner;
}
/**
* Gets the path for the LockedObject
*
* @return path
*/
public String getPath() {
return _path;
}
/**
* Sets the exclusivity for the LockedObject
*
* @param exclusive
*/
public void setExclusive(boolean exclusive) {
_exclusive = exclusive;
}
/**
* Gets the exclusivity for the LockedObject
*
* @return exclusivity
*/
public boolean isExclusive() {
return _exclusive;
}
/**
* Gets the exclusivity for the LockedObject
*
* @return exclusivity
*/
public boolean isShared() {
return !_exclusive;
}
/**
* Gets the type of the lock
*
* @return type
*/
public String getType() {
return _type;
}
/**
* Gets the depth of the lock
*
* @return depth
*/
public int getLockDepth() {
return _lockDepth;
}
}

View File

@ -0,0 +1,384 @@
/*
* Copyright 2005-2006 webdav-servlet group.
*
* Licensed 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 net.sf.webdav.locking;
import java.util.Enumeration;
import java.util.Hashtable;
import net.sf.webdav.ITransaction;
import net.sf.webdav.exceptions.LockFailedException;
/**
* simple locking management for concurrent data access, NOT the webdav locking.
* ( could that be used instead? )
*
* IT IS ACTUALLY USED FOR DOLOCK
*
* @author re
*/
public class ResourceLocks implements IResourceLocks {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(ResourceLocks.class);
/**
* after creating this much LockedObjects, a cleanup deletes unused
* LockedObjects
*/
private final int _cleanupLimit = 100000;
protected int _cleanupCounter = 0;
/**
* keys: path value: LockedObject from that path
*/
protected Hashtable<String, LockedObject> _locks = new Hashtable<String, LockedObject>();
/**
* keys: id value: LockedObject from that id
*/
protected Hashtable<String, LockedObject> _locksByID = new Hashtable<String, LockedObject>();
/**
* keys: path value: Temporary LockedObject from that path
*/
protected Hashtable<String, LockedObject> _tempLocks = new Hashtable<String, LockedObject>();
/**
* keys: id value: Temporary LockedObject from that id
*/
protected Hashtable<String, LockedObject> _tempLocksByID = new Hashtable<String, LockedObject>();
// REMEMBER TO REMOVE UNUSED LOCKS FROM THE HASHTABLE AS WELL
protected LockedObject _root = null;
protected LockedObject _tempRoot = null;
private boolean _temporary = true;
public ResourceLocks() {
_root = new LockedObject(this, "/", true);
_tempRoot = new LockedObject(this, "/", false);
}
public synchronized boolean lock(ITransaction transaction, String path,
String owner, boolean exclusive, int depth, int timeout,
boolean temporary) throws LockFailedException {
LockedObject lo = null;
if (temporary) {
lo = generateTempLockedObjects(transaction, path);
lo._type = "read";
} else {
lo = generateLockedObjects(transaction, path);
lo._type = "write";
}
if (lo.checkLocks(exclusive, depth)) {
lo._exclusive = exclusive;
lo._lockDepth = depth;
lo._expiresAt = System.currentTimeMillis() + (timeout * 1000);
if (lo._parent != null) {
lo._parent._expiresAt = lo._expiresAt;
if (lo._parent.equals(_root)) {
LockedObject rootLo = getLockedObjectByPath(transaction,
_root.getPath());
rootLo._expiresAt = lo._expiresAt;
} else if (lo._parent.equals(_tempRoot)) {
LockedObject tempRootLo = getTempLockedObjectByPath(
transaction, _tempRoot.getPath());
tempRootLo._expiresAt = lo._expiresAt;
}
}
if (lo.addLockedObjectOwner(owner)) {
return true;
} else {
LOG.trace("Couldn't set owner \"" + owner
+ "\" to resource at '" + path + "'");
return false;
}
} else {
// can not lock
LOG.trace("Lock resource at " + path + " failed because"
+ "\na parent or child resource is currently locked");
return false;
}
}
public synchronized boolean unlock(ITransaction transaction, String id,
String owner) {
if (_locksByID.containsKey(id)) {
String path = _locksByID.get(id).getPath();
if (_locks.containsKey(path)) {
LockedObject lo = _locks.get(path);
lo.removeLockedObjectOwner(owner);
if (lo._children == null && lo._owner == null)
lo.removeLockedObject();
} else {
// there is no lock at that path. someone tried to unlock it
// anyway. could point to a problem
LOG
.trace("net.sf.webdav.locking.ResourceLocks.unlock(): no lock for path "
+ path);
return false;
}
if (_cleanupCounter > _cleanupLimit) {
_cleanupCounter = 0;
cleanLockedObjects(transaction, _root, !_temporary);
}
}
checkTimeouts(transaction, !_temporary);
return true;
}
public synchronized void unlockTemporaryLockedObjects(
ITransaction transaction, String path, String owner) {
if (_tempLocks.containsKey(path)) {
LockedObject lo = _tempLocks.get(path);
lo.removeLockedObjectOwner(owner);
} else {
// there is no lock at that path. someone tried to unlock it
// anyway. could point to a problem
LOG
.trace("net.sf.webdav.locking.ResourceLocks.unlock(): no lock for path "
+ path);
}
if (_cleanupCounter > _cleanupLimit) {
_cleanupCounter = 0;
cleanLockedObjects(transaction, _tempRoot, _temporary);
}
checkTimeouts(transaction, _temporary);
}
public void checkTimeouts(ITransaction transaction, boolean temporary) {
if (!temporary) {
Enumeration<LockedObject> lockedObjects = _locks.elements();
while (lockedObjects.hasMoreElements()) {
LockedObject currentLockedObject = lockedObjects.nextElement();
if (currentLockedObject._expiresAt < System.currentTimeMillis()) {
currentLockedObject.removeLockedObject();
}
}
} else {
Enumeration<LockedObject> lockedObjects = _tempLocks.elements();
while (lockedObjects.hasMoreElements()) {
LockedObject currentLockedObject = lockedObjects.nextElement();
if (currentLockedObject._expiresAt < System.currentTimeMillis()) {
currentLockedObject.removeTempLockedObject();
}
}
}
}
public boolean exclusiveLock(ITransaction transaction, String path,
String owner, int depth, int timeout) throws LockFailedException {
return lock(transaction, path, owner, true, depth, timeout, false);
}
public boolean sharedLock(ITransaction transaction, String path,
String owner, int depth, int timeout) throws LockFailedException {
return lock(transaction, path, owner, false, depth, timeout, false);
}
public LockedObject getLockedObjectByID(ITransaction transaction, String id) {
if (_locksByID.containsKey(id)) {
return _locksByID.get(id);
} else {
return null;
}
}
public LockedObject getLockedObjectByPath(ITransaction transaction,
String path) {
if (_locks.containsKey(path)) {
return (LockedObject) this._locks.get(path);
} else {
return null;
}
}
public LockedObject getTempLockedObjectByID(ITransaction transaction,
String id) {
if (_tempLocksByID.containsKey(id)) {
return _tempLocksByID.get(id);
} else {
return null;
}
}
public LockedObject getTempLockedObjectByPath(ITransaction transaction,
String path) {
if (_tempLocks.containsKey(path)) {
return (LockedObject) this._tempLocks.get(path);
} else {
return null;
}
}
/**
* generates real LockedObjects for the resource at path and its parent
* folders. does not create new LockedObjects if they already exist
*
* @param transaction
* @param path
* path to the (new) LockedObject
* @return the LockedObject for path.
*/
private LockedObject generateLockedObjects(ITransaction transaction,
String path) {
if (!_locks.containsKey(path)) {
LockedObject returnObject = new LockedObject(this, path,
!_temporary);
String parentPath = getParentPath(path);
if (parentPath != null) {
LockedObject parentLockedObject = generateLockedObjects(
transaction, parentPath);
parentLockedObject.addChild(returnObject);
returnObject._parent = parentLockedObject;
}
return returnObject;
} else {
// there is already a LockedObject on the specified path
return (LockedObject) this._locks.get(path);
}
}
/**
* generates temporary LockedObjects for the resource at path and its parent
* folders. does not create new LockedObjects if they already exist
*
* @param transaction
* @param path
* path to the (new) LockedObject
* @return the LockedObject for path.
*/
private LockedObject generateTempLockedObjects(ITransaction transaction,
String path) {
if (!_tempLocks.containsKey(path)) {
LockedObject returnObject = new LockedObject(this, path, _temporary);
String parentPath = getParentPath(path);
if (parentPath != null) {
LockedObject parentLockedObject = generateTempLockedObjects(
transaction, parentPath);
parentLockedObject.addChild(returnObject);
returnObject._parent = parentLockedObject;
}
return returnObject;
} else {
// there is already a LockedObject on the specified path
return (LockedObject) this._tempLocks.get(path);
}
}
/**
* deletes unused LockedObjects and resets the counter. works recursively
* starting at the given LockedObject
*
* @param transaction
* @param lo
* LockedObject
* @param temporary
* Clean temporary or real locks
*
* @return if cleaned
*/
private boolean cleanLockedObjects(ITransaction transaction,
LockedObject lo, boolean temporary) {
if (lo._children == null) {
if (lo._owner == null) {
if (temporary) {
lo.removeTempLockedObject();
} else {
lo.removeLockedObject();
}
return true;
} else {
return false;
}
} else {
boolean canDelete = true;
int limit = lo._children.length;
for (int i = 0; i < limit; i++) {
if (!cleanLockedObjects(transaction, lo._children[i], temporary)) {
canDelete = false;
} else {
// because the deleting shifts the array
i--;
limit--;
}
}
if (canDelete) {
if (lo._owner == null) {
if (temporary) {
lo.removeTempLockedObject();
} else {
lo.removeLockedObject();
}
return true;
} else {
return false;
}
} else {
return false;
}
}
}
/**
* creates the parent path from the given path by removing the last '/' and
* everything after that
*
* @param path
* the path
* @return parent path
*/
private String getParentPath(String path) {
int slash = path.lastIndexOf('/');
if (slash == -1) {
return null;
} else {
if (slash == 0) {
// return "root" if parent path is empty string
return "/";
} else {
return path.substring(0, slash);
}
}
}
}

View File

@ -0,0 +1,596 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import net.sf.webdav.IMethodExecutor;
import net.sf.webdav.ITransaction;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.fromcatalina.RequestUtil;
import net.sf.webdav.fromcatalina.URLEncoder;
import net.sf.webdav.fromcatalina.XMLWriter;
import net.sf.webdav.locking.IResourceLocks;
import net.sf.webdav.locking.LockedObject;
public abstract class AbstractMethod implements IMethodExecutor {
private static final ThreadLocal<DateFormat> thLastmodifiedDateFormat = new ThreadLocal<DateFormat>();
private static final ThreadLocal<DateFormat> thCreationDateFormat = new ThreadLocal<DateFormat>();
private static final ThreadLocal<DateFormat> thLocalDateFormat = new ThreadLocal<DateFormat>();
/**
* Array containing the safe characters set.
*/
protected static URLEncoder URL_ENCODER;
/**
* Default depth is infite.
*/
protected static final int INFINITY = 3;
/**
* Simple date format for the creation date ISO 8601 representation
* (partial).
*/
protected static final String CREATION_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
/**
* Simple date format for the last modified date. (RFC 822 updated by RFC
* 1123)
*/
protected static final String LAST_MODIFIED_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
protected static final String LOCAL_DATE_FORMAT = "dd/MM/yy' 'HH:mm:ss";
static {
/**
* GMT timezone - all HTTP dates are on GMT
*/
URL_ENCODER = new URLEncoder();
URL_ENCODER.addSafeCharacter('-');
URL_ENCODER.addSafeCharacter('_');
URL_ENCODER.addSafeCharacter('.');
URL_ENCODER.addSafeCharacter('*');
URL_ENCODER.addSafeCharacter('/');
}
/**
* size of the io-buffer
*/
protected static int BUF_SIZE = 65536;
/**
* Default lock timeout value.
*/
protected static final int DEFAULT_TIMEOUT = 3600;
/**
* Maximum lock timeout.
*/
protected static final int MAX_TIMEOUT = 604800;
/**
* Boolean value to temporary lock resources (for method locks)
*/
protected static final boolean TEMPORARY = true;
/**
* Timeout for temporary locks
*/
protected static final int TEMP_TIMEOUT = 10;
public static String lastModifiedDateFormat(final Date date) {
DateFormat df = thLastmodifiedDateFormat.get();
if( df == null ) {
df = new SimpleDateFormat(LAST_MODIFIED_DATE_FORMAT, Locale.US);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
thLastmodifiedDateFormat.set( df );
}
return df.format(date);
}
public static String creationDateFormat(final Date date) {
DateFormat df = thCreationDateFormat.get();
if( df == null ) {
df = new SimpleDateFormat(CREATION_DATE_FORMAT);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
thCreationDateFormat.set( df );
}
return df.format(date);
}
public static String getLocalDateFormat(final Date date, final Locale loc) {
DateFormat df = thLocalDateFormat.get();
if( df == null ) {
df = new SimpleDateFormat(LOCAL_DATE_FORMAT, loc);
}
return df.format(date);
}
/**
* Parses and normalizes the destination header.
*
* @param req
* Servlet request
* @param resp
* Servlet response
* @return destinationPath
* @throws IOException
* if an error occurs while sending response
*/
protected String parseDestinationHeader(HttpServletRequest req,
HttpServletResponse resp) throws IOException {
String destinationPath = req.getHeader("Destination");
if (destinationPath == null) {
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
return null;
}
// Remove url encoding from destination
destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
int protocolIndex = destinationPath.indexOf("://");
if (protocolIndex >= 0) {
// if the Destination URL contains the protocol, we can safely
// trim everything upto the first "/" character after "://"
int firstSeparator = destinationPath
.indexOf("/", protocolIndex + 4);
if (firstSeparator < 0) {
destinationPath = "/";
} else {
destinationPath = destinationPath.substring(firstSeparator);
}
} else {
String hostName = req.getServerName();
if ((hostName != null) && (destinationPath.startsWith(hostName))) {
destinationPath = destinationPath.substring(hostName.length());
}
int portIndex = destinationPath.indexOf(":");
if (portIndex >= 0) {
destinationPath = destinationPath.substring(portIndex);
}
if (destinationPath.startsWith(":")) {
int firstSeparator = destinationPath.indexOf("/");
if (firstSeparator < 0) {
destinationPath = "/";
} else {
destinationPath = destinationPath.substring(firstSeparator);
}
}
}
// Normalize destination path (remove '.' and' ..')
destinationPath = normalize(destinationPath);
String contextPath = req.getContextPath();
if ((contextPath != null) && (destinationPath.startsWith(contextPath))) {
destinationPath = destinationPath.substring(contextPath.length());
}
String pathInfo = req.getPathInfo();
if (pathInfo != null) {
String servletPath = req.getServletPath();
if ((servletPath != null)
&& (destinationPath.startsWith(servletPath))) {
destinationPath = destinationPath.substring(servletPath
.length());
}
}
return destinationPath;
}
/**
* Return a context-relative path, beginning with a "/", that represents the
* canonical version of the specified path after ".." and "." elements are
* resolved out. If the specified path attempts to go outside the boundaries
* of the current context (i.e. too many ".." path elements are present),
* return <code>null</code> instead.
*
* @param path
* Path to be normalized
* @return normalized path
*/
protected String normalize(String path) {
if (path == null)
return null;
// Create a place for the normalized path
String normalized = path;
if (normalized.equals("/."))
return "/";
// Normalize the slashes and add leading slash if necessary
if (normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index)
+ normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index)
+ normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2)
+ normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
/**
* Return the relative path associated with this servlet.
*
* @param request
* The servlet request we are processing
*/
protected String getRelativePath(HttpServletRequest request) {
// Are we being processed by a RequestDispatcher.include()?
if (request.getAttribute("javax.servlet.include.request_uri") != null) {
String result = (String) request
.getAttribute("javax.servlet.include.path_info");
// if (result == null)
// result = (String) request
// .getAttribute("javax.servlet.include.servlet_path");
if ((result == null) || (result.equals("")))
result = "/";
return (result);
}
// No, extract the desired path directly from the request
String result = request.getPathInfo();
// if (result == null) {
// result = request.getServletPath();
// }
if ((result == null) || (result.equals(""))) {
result = "/";
}
return (result);
}
/**
* creates the parent path from the given path by removing the last '/' and
* everything after that
*
* @param path
* the path
* @return parent path
*/
protected String getParentPath(String path) {
int slash = path.lastIndexOf('/');
if (slash != -1) {
return path.substring(0, slash);
}
return null;
}
/**
* removes a / at the end of the path string, if present
*
* @param path
* the path
* @return the path without trailing /
*/
protected String getCleanPath(String path) {
if (path.endsWith("/") && path.length() > 1)
path = path.substring(0, path.length() - 1);
return path;
}
/**
* Return JAXP document builder instance.
*/
protected DocumentBuilder getDocumentBuilder() throws ServletException {
DocumentBuilder documentBuilder = null;
DocumentBuilderFactory documentBuilderFactory = null;
try {
documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilder = documentBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new ServletException("jaxp failed");
}
return documentBuilder;
}
/**
* reads the depth header from the request and returns it as a int
*
* @param req
* @return the depth from the depth header
*/
protected int getDepth(HttpServletRequest req) {
int depth = INFINITY;
String depthStr = req.getHeader("Depth");
if (depthStr != null) {
if (depthStr.equals("0")) {
depth = 0;
} else if (depthStr.equals("1")) {
depth = 1;
}
}
return depth;
}
/**
* URL rewriter.
*
* @param path
* Path which has to be rewiten
* @return the rewritten path
*/
protected String rewriteUrl(String path) {
return URL_ENCODER.encode(path);
}
/**
* Get the ETag associated with a file.
*
* @param StoredObject
* StoredObject to get resourceLength, lastModified and a hashCode of
* StoredObject
* @return the ETag
*/
protected String getETag(StoredObject so) {
String resourceLength = "";
String lastModified = "";
if (so != null && so.isResource()) {
resourceLength = new Long(so.getResourceLength()).toString();
lastModified = new Long(so.getLastModified().getTime()).toString();
}
return "W/\"" + resourceLength + "-" + lastModified + "\"";
}
protected String[] getLockIdFromIfHeader(HttpServletRequest req) {
String[] ids = new String[2];
String id = req.getHeader("If");
if (id != null && !id.equals("")) {
if (id.indexOf(">)") == id.lastIndexOf(">)")) {
id = id.substring(id.indexOf("(<"), id.indexOf(">)"));
if (id.indexOf("locktoken:") != -1) {
id = id.substring(id.indexOf(':') + 1);
}
ids[0] = id;
} else {
String firstId = id.substring(id.indexOf("(<"), id
.indexOf(">)"));
if (firstId.indexOf("locktoken:") != -1) {
firstId = firstId.substring(firstId.indexOf(':') + 1);
}
ids[0] = firstId;
String secondId = id.substring(id.lastIndexOf("(<"), id
.lastIndexOf(">)"));
if (secondId.indexOf("locktoken:") != -1) {
secondId = secondId.substring(secondId.indexOf(':') + 1);
}
ids[1] = secondId;
}
} else {
ids = null;
}
return ids;
}
protected String getLockIdFromLockTokenHeader(HttpServletRequest req) {
String id = req.getHeader("Lock-Token");
if (id != null) {
id = id.substring(id.indexOf(":") + 1, id.indexOf(">"));
}
return id;
}
/**
* Checks if locks on resources at the given path exists and if so checks
* the If-Header to make sure the If-Header corresponds to the locked
* resource. Returning true if no lock exists or the If-Header is
* corresponding to the locked resource
*
* @param req
* Servlet request
* @param resp
* Servlet response
* @param resourceLocks
* @param path
* path to the resource
* @param errorList
* List of error to be displayed
* @return true if no lock on a resource with the given path exists or if
* the If-Header corresponds to the locked resource
* @throws IOException
* @throws LockFailedException
*/
protected boolean checkLocks(ITransaction transaction,
HttpServletRequest req, HttpServletResponse resp,
IResourceLocks resourceLocks, String path) throws IOException,
LockFailedException {
LockedObject loByPath = resourceLocks.getLockedObjectByPath(
transaction, path);
if (loByPath != null) {
if (loByPath.isShared())
return true;
// the resource is locked
String[] lockTokens = getLockIdFromIfHeader(req);
String lockToken = null;
if (lockTokens != null)
lockToken = lockTokens[0];
else {
return false;
}
if (lockToken != null) {
LockedObject loByIf = resourceLocks.getLockedObjectByID(
transaction, lockToken);
if (loByIf == null) {
// no locked resource to the given lockToken
return false;
}
if (!loByIf.equals(loByPath)) {
loByIf = null;
return false;
}
loByIf = null;
}
}
loByPath = null;
return true;
}
/**
* Send a multistatus element containing a complete error report to the
* client. If the errorList contains only one error, send the error
* directly without wrapping it in a multistatus message.
*
* @param req
* Servlet request
* @param resp
* Servlet response
* @param errorList
* List of error to be displayed
*/
protected void sendReport(HttpServletRequest req, HttpServletResponse resp,
Hashtable<String, Integer> errorList) throws IOException {
if (errorList.size() == 1) {
int code = errorList.elements().nextElement();
if (WebdavStatus.getStatusText(code) != "") {
resp.sendError(code, WebdavStatus.getStatusText(code));
} else {
resp.sendError(code);
}
}
else
{
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
String absoluteUri = req.getRequestURI();
// String relativePath = getRelativePath(req);
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("DAV:", "D");
XMLWriter generatedXML = new XMLWriter(namespaces);
generatedXML.writeXMLHeader();
generatedXML.writeElement("DAV::multistatus", XMLWriter.OPENING);
Enumeration<String> pathList = errorList.keys();
while (pathList.hasMoreElements()) {
String errorPath = (String) pathList.nextElement();
int errorCode = ((Integer) errorList.get(errorPath)).intValue();
generatedXML.writeElement("DAV::response", XMLWriter.OPENING);
generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
String toAppend = null;
if (absoluteUri.endsWith(errorPath)) {
toAppend = absoluteUri;
} else if (absoluteUri.contains(errorPath)) {
int endIndex = absoluteUri.indexOf(errorPath)
+ errorPath.length();
toAppend = absoluteUri.substring(0, endIndex);
}
if (!toAppend.startsWith("/") && !toAppend.startsWith("http:"))
toAppend = "/" + toAppend;
generatedXML.writeText(errorPath);
generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
generatedXML.writeText("HTTP/1.1 " + errorCode + " "
+ WebdavStatus.getStatusText(errorCode));
generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::response", XMLWriter.CLOSING);
}
generatedXML.writeElement("DAV::multistatus", XMLWriter.CLOSING);
Writer writer = resp.getWriter();
writer.write(generatedXML.toString());
writer.close();
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import net.sf.webdav.StoredObject;
public abstract class DeterminableMethod extends AbstractMethod {
private static final String NULL_RESOURCE_METHODS_ALLOWED = "OPTIONS, MKCOL, PUT, PROPFIND, LOCK, UNLOCK";
private static final String RESOURCE_METHODS_ALLOWED = "OPTIONS, GET, HEAD, POST, DELETE, TRACE"
+ ", PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND";
private static final String FOLDER_METHOD_ALLOWED = ", PUT";
private static final String LESS_ALLOWED_METHODS = "OPTIONS, MKCOL, PUT";
/**
* Determines the methods normally allowed for the resource.
*
* @param so
* StoredObject representing the resource
* @return all allowed methods, separated by commas
*/
protected static String determineMethodsAllowed(StoredObject so) {
try {
if (so != null) {
if (so.isNullResource()) {
return NULL_RESOURCE_METHODS_ALLOWED;
} else if (so.isFolder()) {
return RESOURCE_METHODS_ALLOWED + FOLDER_METHOD_ALLOWED;
}
// else resource
return RESOURCE_METHODS_ALLOWED;
}
} catch (Exception e) {
// we do nothing, just return less allowed methods
}
return LESS_ALLOWED_METHODS;
}
}

View File

@ -0,0 +1,348 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
import net.sf.webdav.exceptions.ObjectNotFoundException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.fromcatalina.RequestUtil;
import net.sf.webdav.locking.ResourceLocks;
public class DoCopy extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoCopy.class);
private IWebdavStore _store;
private ResourceLocks _resourceLocks;
private DoDelete _doDelete;
private boolean _readOnly;
public DoCopy(IWebdavStore store, ResourceLocks resourceLocks,
DoDelete doDelete, boolean readOnly) {
_store = store;
_resourceLocks = resourceLocks;
_doDelete = doDelete;
_readOnly = readOnly;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
String path = getRelativePath(req);
if (!_readOnly) {
String tempLockOwner = "doCopy" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
TEMP_TIMEOUT, TEMPORARY)) {
try {
if (!copyResource(transaction, req, resp))
return;
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (ObjectAlreadyExistsException e) {
resp.sendError(WebdavStatus.SC_CONFLICT, req
.getRequestURI());
} catch (ObjectNotFoundException e) {
resp.sendError(WebdavStatus.SC_NOT_FOUND, req
.getRequestURI());
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
path, tempLockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
}
}
/**
* Copy a resource.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param req
* Servlet request
* @param resp
* Servlet response
* @return true if the copy is successful
* @throws WebdavException
* if an error in the underlying store occurs
* @throws IOException
* when an error occurs while sending the response
* @throws LockFailedException
*/
public boolean copyResource(ITransaction transaction,
HttpServletRequest req, HttpServletResponse resp)
throws WebdavException, IOException, LockFailedException {
// Parsing destination header
String destinationPath = parseDestinationHeader(req, resp);
if (destinationPath == null)
return false;
String path = getRelativePath(req);
if (path.equals(destinationPath)) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return false;
}
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
String parentDestinationPath = getParentPath(getCleanPath(destinationPath));
if (!checkLocks(transaction, req, resp, _resourceLocks,
parentDestinationPath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return false; // parentDestination is locked
}
if (!checkLocks(transaction, req, resp, _resourceLocks, destinationPath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return false; // destination is locked
}
// Parsing overwrite header
boolean overwrite = true;
String overwriteHeader = req.getHeader("Overwrite");
if (overwriteHeader != null) {
overwrite = overwriteHeader.equalsIgnoreCase("T");
}
// Overwriting the destination
String lockOwner = "copyResource" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, destinationPath, lockOwner, false,
0, TEMP_TIMEOUT, TEMPORARY)) {
StoredObject copySo, destinationSo = null;
try {
copySo = _store.getStoredObject(transaction, path);
// Retrieve the resources
if (copySo == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return false;
}
if (copySo.isNullResource()) {
String methodsAllowed = DeterminableMethod
.determineMethodsAllowed(copySo);
resp.addHeader("Allow", methodsAllowed);
resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
return false;
}
errorList = new Hashtable<String, Integer>();
destinationSo = _store.getStoredObject(transaction,
destinationPath);
if (overwrite) {
// Delete destination resource, if it exists
if (destinationSo != null) {
_doDelete.deleteResource(transaction, destinationPath,
errorList, req, resp);
} else {
resp.setStatus(WebdavStatus.SC_CREATED);
}
} else {
// If the destination exists, then it's a conflict
if (destinationSo != null) {
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
return false;
} else {
resp.setStatus(WebdavStatus.SC_CREATED);
}
}
copy(transaction, path, destinationPath, errorList, req, resp);
if (!errorList.isEmpty()) {
sendReport(req, resp, errorList);
}
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
destinationPath, lockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
return false;
}
return true;
}
/**
* copies the specified resource(s) to the specified destination.
* preconditions must be handled by the caller. Standard status codes must
* be handled by the caller. a multi status report in case of errors is
* created here.
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param sourcePath
* path from where to read
* @param destinationPath
* path where to write
* @param req
* HttpServletRequest
* @param resp
* HttpServletResponse
* @throws WebdavException
* if an error in the underlying store occurs
* @throws IOException
*/
private void copy(ITransaction transaction, String sourcePath,
String destinationPath, Hashtable<String, Integer> errorList,
HttpServletRequest req, HttpServletResponse resp)
throws WebdavException, IOException {
StoredObject sourceSo = _store.getStoredObject(transaction, sourcePath);
if (sourceSo.isResource()) {
_store.createResource(transaction, destinationPath);
long resourceLength = _store.setResourceContent(transaction,
destinationPath, _store.getResourceContent(transaction,
sourcePath), null, null);
if (resourceLength != -1) {
StoredObject destinationSo = _store.getStoredObject(
transaction, destinationPath);
destinationSo.setResourceLength(resourceLength);
}
} else {
if (sourceSo.isFolder()) {
copyFolder(transaction, sourcePath, destinationPath, errorList,
req, resp);
} else {
resp.sendError(WebdavStatus.SC_NOT_FOUND);
}
}
}
/**
* helper method of copy() recursively copies the FOLDER at source path to
* destination path
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param sourcePath
* where to read
* @param destinationPath
* where to write
* @param errorList
* all errors that ocurred
* @param req
* HttpServletRequest
* @param resp
* HttpServletResponse
* @throws WebdavException
* if an error in the underlying store occurs
*/
private void copyFolder(ITransaction transaction, String sourcePath,
String destinationPath, Hashtable<String, Integer> errorList,
HttpServletRequest req, HttpServletResponse resp)
throws WebdavException {
_store.createFolder(transaction, destinationPath);
boolean infiniteDepth = true;
String depth = req.getHeader("Depth");
if (depth != null) {
if (depth.equals("0")) {
infiniteDepth = false;
}
}
if (infiniteDepth) {
String[] children = _store
.getChildrenNames(transaction, sourcePath);
children = children == null ? new String[] {} : children;
StoredObject childSo;
for (int i = children.length - 1; i >= 0; i--) {
children[i] = "/" + children[i];
try {
childSo = _store.getStoredObject(transaction,
(sourcePath + children[i]));
if (childSo.isResource()) {
_store.createResource(transaction, destinationPath
+ children[i]);
long resourceLength = _store.setResourceContent(
transaction, destinationPath + children[i],
_store.getResourceContent(transaction,
sourcePath + children[i]), null, null);
if (resourceLength != -1) {
StoredObject destinationSo = _store
.getStoredObject(transaction,
destinationPath + children[i]);
destinationSo.setResourceLength(resourceLength);
}
} else {
copyFolder(transaction, sourcePath + children[i],
destinationPath + children[i], errorList, req,
resp);
}
} catch (AccessDeniedException e) {
errorList.put(destinationPath + children[i], new Integer(
WebdavStatus.SC_FORBIDDEN));
} catch (ObjectNotFoundException e) {
errorList.put(destinationPath + children[i], new Integer(
WebdavStatus.SC_NOT_FOUND));
} catch (ObjectAlreadyExistsException e) {
errorList.put(destinationPath + children[i], new Integer(
WebdavStatus.SC_CONFLICT));
} catch (WebdavException e) {
errorList.put(destinationPath + children[i], new Integer(
WebdavStatus.SC_INTERNAL_SERVER_ERROR));
}
}
}
}
}

View File

@ -0,0 +1,206 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
import net.sf.webdav.exceptions.ObjectNotFoundException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.locking.ResourceLocks;
public class DoDelete extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoDelete.class);
private IWebdavStore _store;
private ResourceLocks _resourceLocks;
private boolean _readOnly;
public DoDelete(IWebdavStore store, ResourceLocks resourceLocks,
boolean readOnly) {
_store = store;
_resourceLocks = resourceLocks;
_readOnly = readOnly;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
if (!_readOnly) {
String path = getRelativePath(req);
String parentPath = getParentPath(getCleanPath(path));
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // parent is locked
}
if (!checkLocks(transaction, req, resp, _resourceLocks, path)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // resource is locked
}
String tempLockOwner = "doDelete" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
TEMP_TIMEOUT, TEMPORARY)) {
try {
errorList = new Hashtable<String, Integer>();
deleteResource(transaction, path, errorList, req, resp);
if (!errorList.isEmpty()) {
sendReport(req, resp, errorList);
}
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (ObjectAlreadyExistsException e) {
resp.sendError(WebdavStatus.SC_NOT_FOUND, req
.getRequestURI());
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
path, tempLockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
}
}
/**
* deletes the recources at "path"
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param path
* the folder to be deleted
* @param errorList
* all errors that ocurred
* @param req
* HttpServletRequest
* @param resp
* HttpServletResponse
* @throws WebdavException
* if an error in the underlying store occurs
* @throws IOException
* when an error occurs while sending the response
*/
public void deleteResource(ITransaction transaction, String path,
Hashtable<String, Integer> errorList, HttpServletRequest req,
HttpServletResponse resp) throws IOException, WebdavException {
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
if (!_readOnly) {
StoredObject so = _store.getStoredObject(transaction, path);
if (so != null) {
if (so.isResource()) {
_store.removeObject(transaction, path);
} else {
if (so.isFolder()) {
deleteFolder(transaction, path, errorList, req, resp);
_store.removeObject(transaction, path);
} else {
resp.sendError(WebdavStatus.SC_NOT_FOUND);
}
}
} else {
resp.sendError(WebdavStatus.SC_NOT_FOUND);
}
so = null;
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
}
}
/**
*
* helper method of deleteResource() deletes the folder and all of its
* contents
*
* @param transaction
* indicates that the method is within the scope of a WebDAV
* transaction
* @param path
* the folder to be deleted
* @param errorList
* all errors that ocurred
* @param req
* HttpServletRequest
* @param resp
* HttpServletResponse
* @throws WebdavException
* if an error in the underlying store occurs
*/
private void deleteFolder(ITransaction transaction, String path,
Hashtable<String, Integer> errorList, HttpServletRequest req,
HttpServletResponse resp) throws WebdavException {
String[] children = _store.getChildrenNames(transaction, path);
children = children == null ? new String[] {} : children;
StoredObject so = null;
for (int i = children.length - 1; i >= 0; i--) {
children[i] = "/" + children[i];
try {
so = _store.getStoredObject(transaction, path + children[i]);
if (so.isResource()) {
_store.removeObject(transaction, path + children[i]);
} else {
deleteFolder(transaction, path + children[i], errorList,
req, resp);
_store.removeObject(transaction, path + children[i]);
}
} catch (AccessDeniedException e) {
errorList.put(path + children[i], new Integer(
WebdavStatus.SC_FORBIDDEN));
} catch (ObjectNotFoundException e) {
errorList.put(path + children[i], new Integer(
WebdavStatus.SC_NOT_FOUND));
} catch (WebdavException e) {
errorList.put(path + children[i], new Integer(
WebdavStatus.SC_INTERNAL_SERVER_ERROR));
}
}
so = null;
}
}

View File

@ -0,0 +1,307 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.IMimeTyper;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.locking.ResourceLocks;
public class DoGet extends DoHead {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoGet.class);
public DoGet(IWebdavStore store, String dftIndexFile, String insteadOf404,
ResourceLocks resourceLocks, IMimeTyper mimeTyper,
int contentLengthHeader) {
super(store, dftIndexFile, insteadOf404, resourceLocks, mimeTyper,
contentLengthHeader);
}
protected void doBody(ITransaction transaction, HttpServletResponse resp,
String path) {
try {
StoredObject so = _store.getStoredObject(transaction, path);
if (so.isNullResource()) {
String methodsAllowed = DeterminableMethod
.determineMethodsAllowed(so);
resp.addHeader("Allow", methodsAllowed);
resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
return;
}
OutputStream out = resp.getOutputStream();
InputStream in = _store.getResourceContent(transaction, path);
try {
int read = -1;
byte[] copyBuffer = new byte[BUF_SIZE];
while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) {
out.write(copyBuffer, 0, read);
}
} finally {
// flushing causes a IOE if a file is opened on the webserver
// client disconnected before server finished sending response
try {
in.close();
} catch (Exception e) {
LOG.warn("Closing InputStream causes Exception!\n"
+ e.toString());
}
try {
out.flush();
out.close();
} catch (Exception e) {
LOG.warn("Flushing OutputStream causes Exception!\n"
+ e.toString());
}
}
} catch (Exception e) {
LOG.trace(e.toString());
}
}
protected void folderBody(ITransaction transaction, String path,
HttpServletResponse resp, HttpServletRequest req)
throws IOException {
StoredObject so = _store.getStoredObject(transaction, path);
if (so == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND, req
.getRequestURI());
} else {
if (so.isNullResource()) {
String methodsAllowed = DeterminableMethod
.determineMethodsAllowed(so);
resp.addHeader("Allow", methodsAllowed);
resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
return;
}
if (so.isFolder()) {
// TODO some folder response (for browsers, DAV tools
// use propfind) in html?
Locale locale = req.getLocale();
DateFormat shortDF= getDateTimeFormat(req.getLocale());
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF8");
OutputStream out = resp.getOutputStream();
String[] children = _store.getChildrenNames(transaction, path);
// Make sure it's not null
children = children == null ? new String[] {} : children;
// Sort by name
Arrays.sort(children);
StringBuilder childrenTemp = new StringBuilder();
childrenTemp.append("<html><head><title>Content of folder");
childrenTemp.append(path);
childrenTemp.append("</title><style type=\"text/css\">");
childrenTemp.append(getCSS());
childrenTemp.append("</style></head>");
childrenTemp.append("<body>");
childrenTemp.append(getHeader(transaction, path, resp, req));
childrenTemp.append("<table>");
childrenTemp.append("<tr><th>Name</th><th>Size</th><th>Created</th><th>Modified</th></tr>");
childrenTemp.append("<tr>");
childrenTemp.append("<td colspan=\"4\"><a href=\"../\">Parent</a></td></tr>");
boolean isEven= false;
for (String child : children)
{
isEven= !isEven;
childrenTemp.append("<tr class=\"");
childrenTemp.append(isEven ? "even" : "odd");
childrenTemp.append("\">");
childrenTemp.append("<td>");
childrenTemp.append("<a href=\"");
childrenTemp.append(child);
StoredObject obj= _store.getStoredObject(transaction, path+"/"+child);
if (obj == null)
{
LOG.error("Should not return null for "+path+"/"+child);
}
if (obj != null && obj.isFolder())
{
childrenTemp.append("/");
}
childrenTemp.append("\">");
childrenTemp.append(child);
childrenTemp.append("</a></td>");
if (obj != null && obj.isFolder())
{
childrenTemp.append("<td>Folder</td>");
}
else
{
childrenTemp.append("<td>");
if (obj != null )
{
childrenTemp.append(obj.getResourceLength());
}
else
{
childrenTemp.append("Unknown");
}
childrenTemp.append(" Bytes</td>");
}
if (obj != null && obj.getCreationDate() != null)
{
childrenTemp.append("<td>");
childrenTemp.append(shortDF.format(obj.getCreationDate()));
childrenTemp.append("</td>");
}
else
{
childrenTemp.append("<td></td>");
}
if (obj != null && obj.getLastModified() != null)
{
childrenTemp.append("<td>");
childrenTemp.append(shortDF.format(obj.getLastModified()));
childrenTemp.append("</td>");
}
else
{
childrenTemp.append("<td></td>");
}
childrenTemp.append("</tr>");
}
childrenTemp.append("</table>");
childrenTemp.append(getFooter(transaction, path, resp, req));
childrenTemp.append("</body></html>");
out.write(childrenTemp.toString().getBytes("UTF-8"));
}
}
}
/**
* Return the CSS styles used to display the HTML representation
* of the webdav content.
*
* @return
*/
protected String getCSS()
{
// The default styles to use
String retVal= "body {\n"+
" font-family: Arial, Helvetica, sans-serif;\n"+
"}\n"+
"h1 {\n"+
" font-size: 1.5em;\n"+
"}\n"+
"th {\n"+
" background-color: #9DACBF;\n"+
"}\n"+
"table {\n"+
" border-top-style: solid;\n"+
" border-right-style: solid;\n"+
" border-bottom-style: solid;\n"+
" border-left-style: solid;\n"+
"}\n"+
"td {\n"+
" margin: 0px;\n"+
" padding-top: 2px;\n"+
" padding-right: 5px;\n"+
" padding-bottom: 2px;\n"+
" padding-left: 5px;\n"+
"}\n"+
"tr.even {\n"+
" background-color: #CCCCCC;\n"+
"}\n"+
"tr.odd {\n"+
" background-color: #FFFFFF;\n"+
"}\n"+
"";
try
{
// Try loading one via class loader and use that one instead
ClassLoader cl = getClass().getClassLoader();
InputStream iStream = cl.getResourceAsStream("webdav.css");
if(iStream != null)
{
// Found css via class loader, use that one
StringBuffer out = new StringBuffer();
byte[] b = new byte[4096];
for (int n; (n = iStream.read(b)) != -1;)
{
out.append(new String(b, 0, n));
}
retVal= out.toString();
}
}
catch (Exception ex)
{
LOG.error("Error in reading webdav.css", ex);
}
return retVal;
}
/**
* Return the header to be displayed in front of the folder content
*
* @param transaction
* @param path
* @param resp
* @param req
* @return
*/
protected String getHeader(ITransaction transaction, String path,
HttpServletResponse resp, HttpServletRequest req)
{
return "<h1>Content of folder "+path+"</h1>";
}
/**
* Return the footer to be displayed after the folder content
*
* @param transaction
* @param path
* @param resp
* @param req
* @return
*/
protected String getFooter(ITransaction transaction, String path,
HttpServletResponse resp, HttpServletRequest req)
{
return "";
}
/**
* Return this as the Date/Time format for displaying Creation + Modification dates
*
* @param browserLocale
* @return DateFormat used to display creation and modification dates
*/
protected DateFormat getDateTimeFormat(Locale browserLocale)
{
return SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.SHORT, SimpleDateFormat.MEDIUM, browserLocale);
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.IMimeTyper;
import net.sf.webdav.StoredObject;
import net.sf.webdav.ITransaction;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.locking.ResourceLocks;
public class DoHead extends AbstractMethod {
protected String _dftIndexFile;
protected IWebdavStore _store;
protected String _insteadOf404;
protected ResourceLocks _resourceLocks;
protected IMimeTyper _mimeTyper;
protected int _contentLength;
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoHead.class);
public DoHead(IWebdavStore store, String dftIndexFile, String insteadOf404,
ResourceLocks resourceLocks, IMimeTyper mimeTyper,
int contentLengthHeader) {
_store = store;
_dftIndexFile = dftIndexFile;
_insteadOf404 = insteadOf404;
_resourceLocks = resourceLocks;
_mimeTyper = mimeTyper;
_contentLength = contentLengthHeader;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
// determines if the uri exists.
boolean bUriExists = false;
String path = getRelativePath(req);
LOG.trace("-- " + this.getClass().getName());
StoredObject so;
try {
so = _store.getStoredObject(transaction, path);
if (so == null) {
if (this._insteadOf404 != null && !_insteadOf404.trim().equals("")) {
path = this._insteadOf404;
so = _store.getStoredObject(transaction, this._insteadOf404);
}
} else
bUriExists = true;
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
if (so != null) {
if (so.isFolder()) {
if (_dftIndexFile != null && !_dftIndexFile.trim().equals("")) {
resp.sendRedirect(resp.encodeRedirectURL(req
.getRequestURI()
+ this._dftIndexFile));
return;
}
} else if (so.isNullResource()) {
String methodsAllowed = DeterminableMethod
.determineMethodsAllowed(so);
resp.addHeader("Allow", methodsAllowed);
resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
return;
}
String tempLockOwner = "doGet" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
TEMP_TIMEOUT, TEMPORARY)) {
try {
String eTagMatch = req.getHeader("If-None-Match");
if (eTagMatch != null) {
if (eTagMatch.equals(getETag(so))) {
resp.setStatus(WebdavStatus.SC_NOT_MODIFIED);
return;
}
}
if (so.isResource()) {
// path points to a file but ends with / or \
if (path.endsWith("/") || (path.endsWith("\\"))) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND,
req.getRequestURI());
} else {
// setting headers
long lastModified = so.getLastModified().getTime();
resp.setDateHeader("last-modified", lastModified);
String eTag = getETag(so);
resp.addHeader("ETag", eTag);
long resourceLength = so.getResourceLength();
if (_contentLength == 1) {
if (resourceLength > 0) {
if (resourceLength <= Integer.MAX_VALUE) {
resp
.setContentLength((int) resourceLength);
} else {
resp.setHeader("content-length", ""
+ resourceLength);
// is "content-length" the right header?
// is long a valid format?
}
}
}
String mimeType = _mimeTyper.getMimeType(transaction, path);
if (mimeType != null) {
resp.setContentType(mimeType);
} else {
int lastSlash = path.replace('\\', '/')
.lastIndexOf('/');
int lastDot = path.indexOf(".", lastSlash);
if (lastDot == -1) {
resp.setContentType("text/html");
}
}
doBody(transaction, resp, path);
}
} else {
folderBody(transaction, path, resp, req);
}
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (ObjectAlreadyExistsException e) {
resp.sendError(WebdavStatus.SC_NOT_FOUND, req
.getRequestURI());
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
path, tempLockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
folderBody(transaction, path, resp, req);
}
if (!bUriExists)
resp.setStatus(WebdavStatus.SC_NOT_FOUND);
}
protected void folderBody(ITransaction transaction, String path,
HttpServletResponse resp, HttpServletRequest req)
throws IOException {
// no body for HEAD
}
protected void doBody(ITransaction transaction, HttpServletResponse resp,
String path) throws IOException {
// no body for HEAD
}
}

View File

@ -0,0 +1,593 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.fromcatalina.XMLWriter;
import net.sf.webdav.locking.IResourceLocks;
import net.sf.webdav.locking.LockedObject;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class DoLock extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoLock.class);
private IWebdavStore _store;
private IResourceLocks _resourceLocks;
private boolean _readOnly;
private boolean _macLockRequest = false;
private boolean _exclusive = false;
private String _type = null;
private String _lockOwner = null;
private String _path = null;
private String _parentPath = null;
private String _userAgent = null;
public DoLock(IWebdavStore store, IResourceLocks resourceLocks,
boolean readOnly) {
_store = store;
_resourceLocks = resourceLocks;
_readOnly = readOnly;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
if (_readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
} else {
_path = getRelativePath(req);
_parentPath = getParentPath(getCleanPath(_path));
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
if (!checkLocks(transaction, req, resp, _resourceLocks, _path)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // resource is locked
}
if (!checkLocks(transaction, req, resp, _resourceLocks, _parentPath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // parent is locked
}
// Mac OS Finder (whether 10.4.x or 10.5) can't store files
// because executing a LOCK without lock information causes a
// SC_BAD_REQUEST
_userAgent = req.getHeader("User-Agent");
if (_userAgent != null && _userAgent.indexOf("Darwin") != -1) {
_macLockRequest = true;
String timeString = new Long(System.currentTimeMillis())
.toString();
_lockOwner = _userAgent.concat(timeString);
}
String tempLockOwner = "doLock" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, _path, tempLockOwner, false,
0, TEMP_TIMEOUT, TEMPORARY)) {
try {
if (req.getHeader("If") != null) {
doRefreshLock(transaction, req, resp);
} else {
doLock(transaction, req, resp);
}
} catch (LockFailedException e) {
resp.sendError(WebdavStatus.SC_LOCKED);
LOG.error("Lockfailed exception", e);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
_path, tempLockOwner);
}
}
}
}
private void doLock(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
StoredObject so = _store.getStoredObject(transaction, _path);
if (so != null) {
doLocking(transaction, req, resp);
} else {
// resource doesn't exist, null-resource lock
doNullResourceLock(transaction, req, resp);
}
so = null;
_exclusive = false;
_type = null;
_lockOwner = null;
}
private void doLocking(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException {
// Tests if LockObject on requested path exists, and if so, tests
// exclusivity
LockedObject lo = _resourceLocks.getLockedObjectByPath(transaction,
_path);
if (lo != null) {
if (lo.isExclusive()) {
sendLockFailError(transaction, req, resp);
return;
}
}
try {
// Thats the locking itself
executeLock(transaction, req, resp);
} catch (ServletException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
LOG.trace(e.toString());
} catch (LockFailedException e) {
sendLockFailError(transaction, req, resp);
} finally {
lo = null;
}
}
private void doNullResourceLock(ITransaction transaction,
HttpServletRequest req, HttpServletResponse resp)
throws IOException {
StoredObject parentSo, nullSo = null;
try {
parentSo = _store.getStoredObject(transaction, _parentPath);
if (_parentPath != null && parentSo == null) {
_store.createFolder(transaction, _parentPath);
} else if (_parentPath != null && parentSo != null
&& parentSo.isResource()) {
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
return;
}
nullSo = _store.getStoredObject(transaction, _path);
if (nullSo == null) {
// resource doesn't exist
_store.createResource(transaction, _path);
// Transmit expects 204 response-code, not 201
if (_userAgent != null && _userAgent.indexOf("Transmit") != -1) {
LOG
.trace("DoLock.execute() : do workaround for user agent '"
+ _userAgent + "'");
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
} else {
resp.setStatus(WebdavStatus.SC_CREATED);
}
} else {
// resource already exists, could not execute null-resource lock
sendLockFailError(transaction, req, resp);
return;
}
nullSo = _store.getStoredObject(transaction, _path);
// define the newly created resource as null-resource
nullSo.setNullResource(true);
// Thats the locking itself
executeLock(transaction, req, resp);
} catch (LockFailedException e) {
sendLockFailError(transaction, req, resp);
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
LOG.error("Webdav exception", e);
} catch (ServletException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
LOG.error("Servlet exception", e);
} finally {
parentSo = null;
nullSo = null;
}
}
private void doRefreshLock(ITransaction transaction,
HttpServletRequest req, HttpServletResponse resp)
throws IOException, LockFailedException {
String[] lockTokens = getLockIdFromIfHeader(req);
String lockToken = null;
if (lockTokens != null)
lockToken = lockTokens[0];
if (lockToken != null) {
// Getting LockObject of specified lockToken in If header
LockedObject refreshLo = _resourceLocks.getLockedObjectByID(
transaction, lockToken);
if (refreshLo != null) {
int timeout = getTimeout(transaction, req);
refreshLo.refreshTimeout(timeout);
// sending success response
generateXMLReport(transaction, resp, refreshLo);
refreshLo = null;
} else {
// no LockObject to given lockToken
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
}
} else {
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
}
}
// ------------------------------------------------- helper methods
/**
* Executes the LOCK
*/
private void executeLock(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws LockFailedException, IOException,
ServletException {
// Mac OS lock request workaround
if (_macLockRequest) {
LOG.trace("DoLock.execute() : do workaround for user agent '"
+ _userAgent + "'");
doMacLockRequestWorkaround(transaction, req, resp);
} else {
// Getting LockInformation from request
if (getLockInformation(transaction, req, resp)) {
int depth = getDepth(req);
int lockDuration = getTimeout(transaction, req);
boolean lockSuccess = false;
if (_exclusive) {
lockSuccess = _resourceLocks.exclusiveLock(transaction,
_path, _lockOwner, depth, lockDuration);
} else {
lockSuccess = _resourceLocks.sharedLock(transaction, _path,
_lockOwner, depth, lockDuration);
}
if (lockSuccess) {
// Locks successfully placed - return information about
LockedObject lo = _resourceLocks.getLockedObjectByPath(
transaction, _path);
if (lo != null) {
generateXMLReport(transaction, resp, lo);
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
sendLockFailError(transaction, req, resp);
throw new LockFailedException();
}
} else {
// information for LOCK could not be read successfully
resp.setContentType("text/xml; charset=UTF-8");
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
}
}
}
/**
* Tries to get the LockInformation from LOCK request
*/
private boolean getLockInformation(ITransaction transaction,
HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Node lockInfoNode = null;
DocumentBuilder documentBuilder = null;
documentBuilder = getDocumentBuilder();
try {
Document document = documentBuilder.parse(new InputSource(req
.getInputStream()));
// Get the root element of the document
Element rootElement = document.getDocumentElement();
lockInfoNode = rootElement;
if (lockInfoNode != null) {
NodeList childList = lockInfoNode.getChildNodes();
Node lockScopeNode = null;
Node lockTypeNode = null;
Node lockOwnerNode = null;
Node currentNode = null;
String nodeName = null;
for (int i = 0; i < childList.getLength(); i++) {
currentNode = childList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE
|| currentNode.getNodeType() == Node.TEXT_NODE) {
nodeName = currentNode.getNodeName();
if (nodeName.endsWith("locktype")) {
lockTypeNode = currentNode;
}
if (nodeName.endsWith("lockscope")) {
lockScopeNode = currentNode;
}
if (nodeName.endsWith("owner")) {
lockOwnerNode = currentNode;
}
} else {
return false;
}
}
if (lockScopeNode != null) {
String scope = null;
childList = lockScopeNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
currentNode = childList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
scope = currentNode.getNodeName();
if (scope.endsWith("exclusive")) {
_exclusive = true;
} else if (scope.equals("shared")) {
_exclusive = false;
}
}
}
if (scope == null) {
return false;
}
} else {
return false;
}
if (lockTypeNode != null) {
childList = lockTypeNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
currentNode = childList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
_type = currentNode.getNodeName();
if (_type.endsWith("write")) {
_type = "write";
} else if (_type.equals("read")) {
_type = "read";
}
}
}
if (_type == null) {
return false;
}
} else {
return false;
}
if (lockOwnerNode != null) {
childList = lockOwnerNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
currentNode = childList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE
|| currentNode.getNodeType() == Node.TEXT_NODE) {
_lockOwner = currentNode.getFirstChild()
.getNodeValue();
}
}
}
if (_lockOwner == null) {
return false;
}
} else {
return false;
}
} catch (DOMException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
LOG.error("DOM exception", e);
return false;
} catch (SAXException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
LOG.error("SAX exception", e);
return false;
}
return true;
}
/**
* Ties to read the timeout from request
*/
private int getTimeout(ITransaction transaction, HttpServletRequest req) {
int lockDuration = DEFAULT_TIMEOUT;
String lockDurationStr = req.getHeader("Timeout");
if (lockDurationStr == null) {
lockDuration = DEFAULT_TIMEOUT;
} else {
int commaPos = lockDurationStr.indexOf(',');
// if multiple timeouts, just use the first one
if (commaPos != -1) {
lockDurationStr = lockDurationStr.substring(0, commaPos);
}
if (lockDurationStr.startsWith("Second-")) {
lockDuration = new Integer(lockDurationStr.substring(7))
.intValue();
} else {
if (lockDurationStr.equalsIgnoreCase("infinity")) {
lockDuration = MAX_TIMEOUT;
} else {
try {
lockDuration = new Integer(lockDurationStr).intValue();
} catch (NumberFormatException e) {
lockDuration = MAX_TIMEOUT;
}
}
}
if (lockDuration <= 0) {
lockDuration = DEFAULT_TIMEOUT;
}
if (lockDuration > MAX_TIMEOUT) {
lockDuration = MAX_TIMEOUT;
}
}
return lockDuration;
}
/**
* Generates the response XML with all lock information
*/
private void generateXMLReport(ITransaction transaction,
HttpServletResponse resp, LockedObject lo) throws IOException {
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("DAV:", "D");
resp.setStatus(WebdavStatus.SC_OK);
resp.setContentType("text/xml; charset=UTF-8");
XMLWriter generatedXML = new XMLWriter(resp.getWriter(), namespaces);
generatedXML.writeXMLHeader();
generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.OPENING);
generatedXML.writeElement("DAV::activelock", XMLWriter.OPENING);
generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
generatedXML.writeProperty("DAV::" + _type);
generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
if (_exclusive) {
generatedXML.writeProperty("DAV::exclusive");
} else {
generatedXML.writeProperty("DAV::shared");
}
generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
int depth = lo.getLockDepth();
generatedXML.writeElement("DAV::depth", XMLWriter.OPENING);
if (depth == INFINITY) {
generatedXML.writeText("Infinity");
} else {
generatedXML.writeText(String.valueOf(depth));
}
generatedXML.writeElement("DAV::depth", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::owner", XMLWriter.OPENING);
generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
generatedXML.writeText(_lockOwner);
generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::owner", XMLWriter.CLOSING);
long timeout = lo.getTimeoutMillis();
generatedXML.writeElement("DAV::timeout", XMLWriter.OPENING);
generatedXML.writeText("Second-" + timeout / 1000);
generatedXML.writeElement("DAV::timeout", XMLWriter.CLOSING);
String lockToken = lo.getID();
generatedXML.writeElement("DAV::locktoken", XMLWriter.OPENING);
generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
generatedXML.writeText("opaquelocktoken:" + lockToken);
generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::locktoken", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::activelock", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
resp.addHeader("Lock-Token", "<opaquelocktoken:" + lockToken + ">");
generatedXML.sendData();
}
/**
* Executes the lock for a Mac OS Finder client
*/
private void doMacLockRequestWorkaround(ITransaction transaction,
HttpServletRequest req, HttpServletResponse resp)
throws LockFailedException, IOException {
LockedObject lo;
int depth = getDepth(req);
int lockDuration = getTimeout(transaction, req);
if (lockDuration < 0 || lockDuration > MAX_TIMEOUT)
lockDuration = DEFAULT_TIMEOUT;
boolean lockSuccess = false;
lockSuccess = _resourceLocks.exclusiveLock(transaction, _path,
_lockOwner, depth, lockDuration);
if (lockSuccess) {
// Locks successfully placed - return information about
lo = _resourceLocks.getLockedObjectByPath(transaction, _path);
if (lo != null) {
generateXMLReport(transaction, resp, lo);
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
// Locking was not successful
sendLockFailError(transaction, req, resp);
}
}
/**
* Sends an error report to the client
*/
private void sendLockFailError(ITransaction transaction,
HttpServletRequest req, HttpServletResponse resp)
throws IOException {
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
errorList.put(_path, WebdavStatus.SC_LOCKED);
sendReport(req, resp, errorList);
}
}

View File

@ -0,0 +1,178 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.locking.IResourceLocks;
import net.sf.webdav.locking.LockedObject;
public class DoMkcol extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoMkcol.class);
private IWebdavStore _store;
private IResourceLocks _resourceLocks;
private boolean _readOnly;
public DoMkcol(IWebdavStore store, IResourceLocks resourceLocks,
boolean readOnly) {
_store = store;
_resourceLocks = resourceLocks;
_readOnly = readOnly;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
if (!_readOnly) {
String path = getRelativePath(req);
String parentPath = getParentPath(getCleanPath(path));
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
// TODO remove
LOG
.trace("MkCol on locked resource (parentPath) not executable!"
+ "\n Sending SC_FORBIDDEN (403) error response!");
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
String tempLockOwner = "doMkcol" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
TEMP_TIMEOUT, TEMPORARY)) {
StoredObject parentSo, so = null;
try {
parentSo = _store.getStoredObject(transaction, parentPath);
if (parentSo == null) {
// parent not exists
resp.sendError(WebdavStatus.SC_CONFLICT);
return;
}
if (parentPath != null && parentSo.isFolder()) {
so = _store.getStoredObject(transaction, path);
if (so == null) {
_store.createFolder(transaction, path);
resp.setStatus(WebdavStatus.SC_CREATED);
} else {
// object already exists
if (so.isNullResource()) {
LockedObject nullResourceLo = _resourceLocks
.getLockedObjectByPath(transaction,
path);
if (nullResourceLo == null) {
resp
.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
return;
}
String nullResourceLockToken = nullResourceLo
.getID();
String[] lockTokens = getLockIdFromIfHeader(req);
String lockToken = null;
if (lockTokens != null)
lockToken = lockTokens[0];
else {
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
return;
}
if (lockToken.equals(nullResourceLockToken)) {
so.setNullResource(false);
so.setFolder(true);
String[] nullResourceLockOwners = nullResourceLo
.getOwner();
String owner = null;
if (nullResourceLockOwners != null)
owner = nullResourceLockOwners[0];
if (_resourceLocks.unlock(transaction,
lockToken, owner)) {
resp.setStatus(WebdavStatus.SC_CREATED);
} else {
resp
.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
// TODO remove
LOG
.trace("MkCol on lock-null-resource with wrong lock-token!"
+ "\n Sending multistatus error report!");
errorList.put(path, WebdavStatus.SC_LOCKED);
sendReport(req, resp, errorList);
}
} else {
String methodsAllowed = DeterminableMethod
.determineMethodsAllowed(so);
resp.addHeader("Allow", methodsAllowed);
resp
.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
}
}
} else if (parentPath != null && parentSo.isResource()) {
// TODO remove
LOG
.trace("MkCol on resource is not executable"
+ "\n Sending SC_METHOD_NOT_ALLOWED (405) error response!");
String methodsAllowed = DeterminableMethod
.determineMethodsAllowed(parentSo);
resp.addHeader("Allow", methodsAllowed);
resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
}
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
path, tempLockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
}
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.ObjectAlreadyExistsException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.locking.ResourceLocks;
public class DoMove extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoMove.class);
private IWebdavStore _store;
private ResourceLocks _resourceLocks;
private DoDelete _doDelete;
private DoCopy _doCopy;
private boolean _readOnly;
public DoMove(IWebdavStore _store, ResourceLocks resourceLocks, DoDelete doDelete,
DoCopy doCopy, boolean readOnly) {
this._store = _store;
_resourceLocks = resourceLocks;
_doDelete = doDelete;
_doCopy = doCopy;
_readOnly = readOnly;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
if (!_readOnly) {
LOG.trace("-- " + this.getClass().getName());
String sourcePath = getRelativePath(req);
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
if (!checkLocks(transaction, req, resp, _resourceLocks, sourcePath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return;
}
String destinationPath = parseDestinationHeader(req, resp);
if (destinationPath == null) {
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
return;
}
if (!checkLocks(transaction, req, resp, _resourceLocks,
destinationPath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return;
}
String tempLockOwner = "doMove" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, sourcePath, tempLockOwner,
false, 0, TEMP_TIMEOUT, TEMPORARY)) {
try {
boolean ok = _store.moveObject(transaction, destinationPath, sourcePath);
if (!ok) {
if (_doCopy.copyResource(transaction, req, resp)) {
errorList = new Hashtable<String, Integer>();
_doDelete.deleteResource(transaction, sourcePath,
errorList, req, resp);
if (!errorList.isEmpty()) {
sendReport(req, resp, errorList);
}
}
}
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (ObjectAlreadyExistsException e) {
resp.sendError(WebdavStatus.SC_NOT_FOUND, req
.getRequestURI());
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
sourcePath, tempLockOwner);
}
} else {
errorList.put(req.getHeader("Destination"),
WebdavStatus.SC_LOCKED);
sendReport(req, resp, errorList);
}
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
}
}
}

View File

@ -0,0 +1,31 @@
package net.sf.webdav.methods;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.IMethodExecutor;
import net.sf.webdav.ITransaction;
import net.sf.webdav.WebdavStatus;
public class DoNotImplemented implements IMethodExecutor {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoNotImplemented.class);
private boolean _readOnly;
public DoNotImplemented(boolean readOnly) {
_readOnly = readOnly;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException {
LOG.trace("-- " + req.getMethod());
if (_readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} else
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.StoredObject;
import net.sf.webdav.ITransaction;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.locking.ResourceLocks;
public class DoOptions extends DeterminableMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoOptions.class);
private IWebdavStore _store;
private ResourceLocks _resourceLocks;
public DoOptions(IWebdavStore store, ResourceLocks resLocks) {
_store = store;
_resourceLocks = resLocks;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
String tempLockOwner = "doOptions" + System.currentTimeMillis()
+ req.toString();
String path = getRelativePath(req);
if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
TEMP_TIMEOUT, TEMPORARY)) {
StoredObject so = null;
try {
resp.addHeader("DAV", "1, 2");
so = _store.getStoredObject(transaction, path);
String methodsAllowed = determineMethodsAllowed(so);
resp.addHeader("Allow", methodsAllowed);
resp.addHeader("MS-Author-Via", "DAV");
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction, path,
tempLockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
}
}

View File

@ -0,0 +1,627 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Vector;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import net.sf.webdav.IMimeTyper;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.fromcatalina.URLEncoder;
import net.sf.webdav.fromcatalina.XMLHelper;
import net.sf.webdav.fromcatalina.XMLWriter;
import net.sf.webdav.locking.LockedObject;
import net.sf.webdav.locking.ResourceLocks;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
public class DoPropfind extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoPropfind.class);
/**
* Array containing the safe characters set.
*/
protected static URLEncoder URL_ENCODER;
/**
* PROPFIND - Specify a property mask.
*/
private static final int FIND_BY_PROPERTY = 0;
/**
* PROPFIND - Display all properties.
*/
private static final int FIND_ALL_PROP = 1;
/**
* PROPFIND - Return property names.
*/
private static final int FIND_PROPERTY_NAMES = 2;
private IWebdavStore _store;
private ResourceLocks _resourceLocks;
private IMimeTyper _mimeTyper;
private int _depth;
public DoPropfind(IWebdavStore store, ResourceLocks resLocks,
IMimeTyper mimeTyper) {
_store = store;
_resourceLocks = resLocks;
_mimeTyper = mimeTyper;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
// Retrieve the resources
String path = getCleanPath(getRelativePath(req));
String tempLockOwner = "doPropfind" + System.currentTimeMillis()
+ req.toString();
_depth = getDepth(req);
if (_resourceLocks.lock(transaction, path, tempLockOwner, false,
_depth, TEMP_TIMEOUT, TEMPORARY)) {
StoredObject so = null;
try {
so = _store.getStoredObject(transaction, path);
if (so == null) {
resp.setContentType("text/xml; charset=UTF-8");
resp.sendError(HttpServletResponse.SC_NOT_FOUND, req
.getRequestURI());
return;
}
Vector<String> properties = null;
path = getCleanPath(getRelativePath(req));
int propertyFindType = FIND_ALL_PROP;
Node propNode = null;
if (req.getContentLength() > 0) {
DocumentBuilder documentBuilder = getDocumentBuilder();
try {
Document document = documentBuilder
.parse(new InputSource(req.getInputStream()));
// Get the root element of the document
Element rootElement = document.getDocumentElement();
propNode = XMLHelper
.findSubElement(rootElement, "prop");
if (propNode != null) {
propertyFindType = FIND_BY_PROPERTY;
} else if (XMLHelper.findSubElement(rootElement,
"propname") != null) {
propertyFindType = FIND_PROPERTY_NAMES;
} else if (XMLHelper.findSubElement(rootElement,
"allprop") != null) {
propertyFindType = FIND_ALL_PROP;
}
} catch (Exception e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
return;
}
} else {
// no content, which means it is a allprop request
propertyFindType = FIND_ALL_PROP;
}
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("DAV:", "D");
if (propertyFindType == FIND_BY_PROPERTY) {
propertyFindType = 0;
properties = XMLHelper.getPropertiesFromXML(propNode);
}
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
resp.setContentType("text/xml; charset=UTF-8");
// Create multistatus object
XMLWriter generatedXML = new XMLWriter(resp.getWriter(),
namespaces);
generatedXML.writeXMLHeader();
generatedXML
.writeElement("DAV::multistatus", XMLWriter.OPENING);
if (_depth == 0) {
parseProperties(transaction, req, generatedXML, path,
propertyFindType, properties, _mimeTyper
.getMimeType(transaction, path));
} else {
recursiveParseProperties(transaction, path, req,
generatedXML, propertyFindType, properties, _depth,
_mimeTyper.getMimeType(transaction, path));
}
generatedXML
.writeElement("DAV::multistatus", XMLWriter.CLOSING);
generatedXML.sendData();
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (WebdavException e) {
LOG.warn("Sending internal error!");
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} catch (ServletException e) {
e.printStackTrace(); // To change body of catch statement use
// File | Settings | File Templates.
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction, path,
tempLockOwner);
}
} else {
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
errorList.put(path, WebdavStatus.SC_LOCKED);
sendReport(req, resp, errorList);
}
}
/**
* goes recursive through all folders. used by propfind
*
* @param currentPath
* the current path
* @param req
* HttpServletRequest
* @param generatedXML
* @param propertyFindType
* @param properties
* @param depth
* depth of the propfind
* @throws IOException
* if an error in the underlying store occurs
*/
private void recursiveParseProperties(ITransaction transaction,
String currentPath, HttpServletRequest req, XMLWriter generatedXML,
int propertyFindType, Vector<String> properties, int depth,
String mimeType) throws WebdavException {
parseProperties(transaction, req, generatedXML, currentPath,
propertyFindType, properties, mimeType);
if (depth > 0) {
// no need to get name if depth is already zero
String[] names = _store.getChildrenNames(transaction, currentPath);
names = names == null ? new String[] {} : names;
String newPath = null;
for (String name : names) {
newPath = currentPath;
if (!(newPath.endsWith("/"))) {
newPath += "/";
}
newPath += name;
recursiveParseProperties(transaction, newPath, req,
generatedXML, propertyFindType, properties, depth - 1,
mimeType);
}
}
}
/**
* Propfind helper method.
*
* @param req
* The servlet request
* @param generatedXML
* XML response to the Propfind request
* @param path
* Path of the current resource
* @param type
* Propfind type
* @param propertiesVector
* If the propfind type is find properties by name, then this Vector
* contains those properties
*/
private void parseProperties(ITransaction transaction,
HttpServletRequest req, XMLWriter generatedXML, String path,
int type, Vector<String> propertiesVector, String mimeType)
throws WebdavException {
StoredObject so = _store.getStoredObject(transaction, path);
boolean isFolder = so.isFolder();
final String creationdate = creationDateFormat(so.getCreationDate());
final String lastModified = lastModifiedDateFormat(so.getLastModified());
String resourceLength = String.valueOf(so.getResourceLength());
// ResourceInfo resourceInfo = new ResourceInfo(path, resources);
generatedXML.writeElement("DAV::response", XMLWriter.OPENING);
String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " "
+ WebdavStatus.getStatusText(WebdavStatus.SC_OK));
// Generating href element
generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
String href = req.getContextPath();
String servletPath = req.getServletPath();
if (servletPath != null) {
if ((href.endsWith("/")) && (servletPath.startsWith("/")))
href += servletPath.substring(1);
else
href += servletPath;
}
if ((href.endsWith("/")) && (path.startsWith("/")))
href += path.substring(1);
else
href += path;
if ((isFolder) && (!href.endsWith("/")))
href += "/";
generatedXML.writeText(rewriteUrl(href));
generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
String resourceName = path;
int lastSlash = path.lastIndexOf('/');
if (lastSlash != -1)
resourceName = resourceName.substring(lastSlash + 1);
switch (type) {
case FIND_ALL_PROP:
generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
generatedXML.writeProperty("DAV::creationdate", creationdate);
generatedXML.writeElement("DAV::displayname", XMLWriter.OPENING);
generatedXML.writeData(resourceName);
generatedXML.writeElement("DAV::displayname", XMLWriter.CLOSING);
if (!isFolder) {
generatedXML
.writeProperty("DAV::getlastmodified", lastModified);
generatedXML.writeProperty("DAV::getcontentlength",
resourceLength);
String contentType = mimeType;
if (contentType != null) {
generatedXML.writeProperty("DAV::getcontenttype",
contentType);
}
generatedXML.writeProperty("DAV::getetag", getETag(so));
generatedXML.writeElement("DAV::resourcetype",
XMLWriter.NO_CONTENT);
} else {
generatedXML.writeElement("DAV::resourcetype",
XMLWriter.OPENING);
generatedXML.writeElement("DAV::collection",
XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::resourcetype",
XMLWriter.CLOSING);
}
writeSupportedLockElements(transaction, generatedXML, path);
writeLockDiscoveryElements(transaction, generatedXML, path);
generatedXML.writeProperty("DAV::source", "");
generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
break;
case FIND_PROPERTY_NAMES:
generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
generatedXML
.writeElement("DAV::creationdate", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::displayname", XMLWriter.NO_CONTENT);
if (!isFolder) {
generatedXML.writeElement("DAV::getcontentlanguage",
XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::getcontentlength",
XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::getcontenttype",
XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::getetag", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::getlastmodified",
XMLWriter.NO_CONTENT);
}
generatedXML
.writeElement("DAV::resourcetype", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::supportedlock",
XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::source", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
break;
case FIND_BY_PROPERTY:
Vector<String> propertiesNotFound = new Vector<String>();
// Parse the list of properties
generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
Enumeration<String> properties = propertiesVector.elements();
while (properties.hasMoreElements()) {
String property = (String) properties.nextElement();
if (property.equals("DAV::creationdate")) {
generatedXML.writeProperty("DAV::creationdate",
creationdate);
} else if (property.equals("DAV::displayname")) {
generatedXML.writeElement("DAV::displayname",
XMLWriter.OPENING);
generatedXML.writeData(resourceName);
generatedXML.writeElement("DAV::displayname",
XMLWriter.CLOSING);
} else if (property.equals("DAV::getcontentlanguage")) {
if (isFolder) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeElement("DAV::getcontentlanguage",
XMLWriter.NO_CONTENT);
}
} else if (property.equals("DAV::getcontentlength")) {
if (isFolder) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeProperty("DAV::getcontentlength",
resourceLength);
}
} else if (property.equals("DAV::getcontenttype")) {
if (isFolder) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeProperty("DAV::getcontenttype",
mimeType);
}
} else if (property.equals("DAV::getetag")) {
if (isFolder || so.isNullResource()) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeProperty("DAV::getetag", getETag(so));
}
} else if (property.equals("DAV::getlastmodified")) {
if (isFolder) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeProperty("DAV::getlastmodified",
lastModified);
}
} else if (property.equals("DAV::resourcetype")) {
if (isFolder) {
generatedXML.writeElement("DAV::resourcetype",
XMLWriter.OPENING);
generatedXML.writeElement("DAV::collection",
XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::resourcetype",
XMLWriter.CLOSING);
} else {
generatedXML.writeElement("DAV::resourcetype",
XMLWriter.NO_CONTENT);
}
} else if (property.equals("DAV::source")) {
generatedXML.writeProperty("DAV::source", "");
} else if (property.equals("DAV::supportedlock")) {
writeSupportedLockElements(transaction, generatedXML, path);
} else if (property.equals("DAV::lockdiscovery")) {
writeLockDiscoveryElements(transaction, generatedXML, path);
} else {
propertiesNotFound.addElement(property);
}
}
generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
Enumeration<String> propertiesNotFoundList = propertiesNotFound
.elements();
if (propertiesNotFoundList.hasMoreElements()) {
status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
+ " "
+ WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
generatedXML.writeElement("DAV::propstat", XMLWriter.OPENING);
generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
while (propertiesNotFoundList.hasMoreElements()) {
generatedXML.writeElement((String) propertiesNotFoundList
.nextElement(), XMLWriter.NO_CONTENT);
}
generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::propstat", XMLWriter.CLOSING);
}
break;
}
generatedXML.writeElement("DAV::response", XMLWriter.CLOSING);
so = null;
}
private void writeSupportedLockElements(ITransaction transaction,
XMLWriter generatedXML, String path) {
LockedObject lo = _resourceLocks.getLockedObjectByPath(transaction,
path);
generatedXML.writeElement("DAV::supportedlock", XMLWriter.OPENING);
if (lo == null) {
// both locks (shared/exclusive) can be granted
generatedXML.writeElement("DAV::lockentry", XMLWriter.OPENING);
generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
generatedXML.writeElement("DAV::exclusive", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
generatedXML.writeElement("DAV::write", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockentry", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockentry", XMLWriter.OPENING);
generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
generatedXML.writeElement("DAV::shared", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
generatedXML.writeElement("DAV::write", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockentry", XMLWriter.CLOSING);
} else {
// LockObject exists, checking lock state
// if an exclusive lock exists, no further lock is possible
if (lo.isShared()) {
generatedXML.writeElement("DAV::lockentry", XMLWriter.OPENING);
generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
generatedXML.writeElement("DAV::shared", XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
generatedXML.writeElement("DAV::" + lo.getType(),
XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockentry", XMLWriter.CLOSING);
}
}
generatedXML.writeElement("DAV::supportedlock", XMLWriter.CLOSING);
lo = null;
}
private void writeLockDiscoveryElements(ITransaction transaction,
XMLWriter generatedXML, String path) {
LockedObject lo = _resourceLocks.getLockedObjectByPath(transaction,
path);
if (lo != null && !lo.hasExpired()) {
generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.OPENING);
generatedXML.writeElement("DAV::activelock", XMLWriter.OPENING);
generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING);
generatedXML.writeProperty("DAV::" + lo.getType());
generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING);
if (lo.isExclusive()) {
generatedXML.writeProperty("DAV::exclusive");
} else {
generatedXML.writeProperty("DAV::shared");
}
generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::depth", XMLWriter.OPENING);
if (_depth == INFINITY) {
generatedXML.writeText("Infinity");
} else {
generatedXML.writeText(String.valueOf(_depth));
}
generatedXML.writeElement("DAV::depth", XMLWriter.CLOSING);
String[] owners = lo.getOwner();
if (owners != null) {
for (int i = 0; i < owners.length; i++) {
generatedXML.writeElement("DAV::owner", XMLWriter.OPENING);
generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
generatedXML.writeText(owners[i]);
generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::owner", XMLWriter.CLOSING);
}
} else {
generatedXML.writeElement("DAV::owner", XMLWriter.NO_CONTENT);
}
int timeout = (int) (lo.getTimeoutMillis() / 1000);
String timeoutStr = new Integer(timeout).toString();
generatedXML.writeElement("DAV::timeout", XMLWriter.OPENING);
generatedXML.writeText("Second-" + timeoutStr);
generatedXML.writeElement("DAV::timeout", XMLWriter.CLOSING);
String lockToken = lo.getID();
generatedXML.writeElement("DAV::locktoken", XMLWriter.OPENING);
generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
generatedXML.writeText("opaquelocktoken:" + lockToken);
generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::locktoken", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::activelock", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.CLOSING);
} else {
generatedXML.writeElement("DAV::lockdiscovery",
XMLWriter.NO_CONTENT);
}
lo = null;
}
}

View File

@ -0,0 +1,228 @@
package net.sf.webdav.methods;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.fromcatalina.XMLHelper;
import net.sf.webdav.fromcatalina.XMLWriter;
import net.sf.webdav.locking.LockedObject;
import net.sf.webdav.locking.ResourceLocks;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
public class DoProppatch extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoProppatch.class);
private boolean _readOnly;
private IWebdavStore _store;
private ResourceLocks _resourceLocks;
public DoProppatch(IWebdavStore store, ResourceLocks resLocks,
boolean readOnly) {
_readOnly = readOnly;
_store = store;
_resourceLocks = resLocks;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
if (_readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
String path = getRelativePath(req);
String parentPath = getParentPath(getCleanPath(path));
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // parent is locked
}
if (!checkLocks(transaction, req, resp, _resourceLocks, path)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // resource is locked
}
// TODO for now, PROPPATCH just sends a valid response, stating that
// everything is fine, but doesn't do anything.
// Retrieve the resources
String tempLockOwner = "doProppatch" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
TEMP_TIMEOUT, TEMPORARY)) {
StoredObject so = null;
LockedObject lo = null;
try {
so = _store.getStoredObject(transaction, path);
lo = _resourceLocks.getLockedObjectByPath(transaction,
getCleanPath(path));
if (so == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
// we do not to continue since there is no root
// resource
}
if (so.isNullResource()) {
String methodsAllowed = DeterminableMethod
.determineMethodsAllowed(so);
resp.addHeader("Allow", methodsAllowed);
resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
return;
}
String[] lockTokens = getLockIdFromIfHeader(req);
boolean lockTokenMatchesIfHeader = (lockTokens != null && lockTokens[0].equals(lo.getID()));
if (lo != null && lo.isExclusive() && !lockTokenMatchesIfHeader) {
// Object on specified path is LOCKED
errorList = new Hashtable<String, Integer>();
errorList.put(path, new Integer(WebdavStatus.SC_LOCKED));
sendReport(req, resp, errorList);
return;
}
List<String> toset = null;
List<String> toremove = null;
List<String> tochange = new Vector<String>();
// contains all properties from
// toset and toremove
path = getCleanPath(getRelativePath(req));
Node tosetNode = null;
Node toremoveNode = null;
if (req.getContentLength() != 0) {
DocumentBuilder documentBuilder = getDocumentBuilder();
try {
Document document = documentBuilder
.parse(new InputSource(req.getInputStream()));
// Get the root element of the document
Element rootElement = document.getDocumentElement();
tosetNode = XMLHelper.findSubElement(XMLHelper
.findSubElement(rootElement, "set"), "prop");
toremoveNode = XMLHelper.findSubElement(XMLHelper
.findSubElement(rootElement, "remove"), "prop");
} catch (Exception e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
return;
}
} else {
// no content: error
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
return;
}
HashMap<String, String> namespaces = new HashMap<String, String>();
namespaces.put("DAV:", "D");
if (tosetNode != null) {
toset = XMLHelper.getPropertiesFromXML(tosetNode);
tochange.addAll(toset);
}
if (toremoveNode != null) {
toremove = XMLHelper.getPropertiesFromXML(toremoveNode);
tochange.addAll(toremove);
}
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
resp.setContentType("text/xml; charset=UTF-8");
// Create multistatus object
XMLWriter generatedXML = new XMLWriter(resp.getWriter(),
namespaces);
generatedXML.writeXMLHeader();
generatedXML
.writeElement("DAV::multistatus", XMLWriter.OPENING);
generatedXML.writeElement("DAV::response", XMLWriter.OPENING);
String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK
+ " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
// Generating href element
generatedXML.writeElement("DAV::href", XMLWriter.OPENING);
String href = req.getContextPath();
if ((href.endsWith("/")) && (path.startsWith("/")))
href += path.substring(1);
else
href += path;
if ((so.isFolder()) && (!href.endsWith("/")))
href += "/";
generatedXML.writeText(rewriteUrl(href));
generatedXML.writeElement("DAV::href", XMLWriter.CLOSING);
for (Iterator<String> iter = tochange.iterator(); iter
.hasNext();) {
String property = (String) iter.next();
generatedXML.writeElement("DAV::propstat",
XMLWriter.OPENING);
generatedXML.writeElement("DAV::prop", XMLWriter.OPENING);
generatedXML.writeElement(property, XMLWriter.NO_CONTENT);
generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement("DAV::status", XMLWriter.CLOSING);
generatedXML.writeElement("DAV::propstat",
XMLWriter.CLOSING);
}
generatedXML.writeElement("DAV::response", XMLWriter.CLOSING);
generatedXML
.writeElement("DAV::multistatus", XMLWriter.CLOSING);
generatedXML.sendData();
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} catch (ServletException e) {
e.printStackTrace(); // To change body of catch statement use
// File | Settings | File Templates.
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction, path,
tempLockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
}
}

View File

@ -0,0 +1,195 @@
/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed 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 net.sf.webdav.methods;
import java.io.IOException;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.ITransaction;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.StoredObject;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.exceptions.AccessDeniedException;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.exceptions.WebdavException;
import net.sf.webdav.locking.IResourceLocks;
import net.sf.webdav.locking.LockedObject;
public class DoPut extends AbstractMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoPut.class);
private IWebdavStore _store;
private IResourceLocks _resourceLocks;
private boolean _readOnly;
private boolean _lazyFolderCreationOnPut;
private String _userAgent;
public DoPut(IWebdavStore store, IResourceLocks resLocks, boolean readOnly,
boolean lazyFolderCreationOnPut) {
_store = store;
_resourceLocks = resLocks;
_readOnly = readOnly;
_lazyFolderCreationOnPut = lazyFolderCreationOnPut;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
if (!_readOnly) {
String path = getRelativePath(req);
String parentPath = getParentPath(path);
_userAgent = req.getHeader("User-Agent");
Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
if (!checkLocks(transaction, req, resp, _resourceLocks, parentPath)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // parent is locked
}
if (!checkLocks(transaction, req, resp, _resourceLocks, path)) {
resp.setStatus(WebdavStatus.SC_LOCKED);
return; // resource is locked
}
String tempLockOwner = "doPut" + System.currentTimeMillis()
+ req.toString();
if (_resourceLocks.lock(transaction, path, tempLockOwner, false, 0,
TEMP_TIMEOUT, TEMPORARY)) {
StoredObject parentSo, so = null;
try {
parentSo = _store.getStoredObject(transaction, parentPath);
if (parentPath != null && parentSo != null
&& parentSo.isResource()) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
} else if (parentPath != null && parentSo == null
&& _lazyFolderCreationOnPut) {
_store.createFolder(transaction, parentPath);
} else if (parentPath != null && parentSo == null
&& !_lazyFolderCreationOnPut) {
errorList.put(parentPath, WebdavStatus.SC_NOT_FOUND);
sendReport(req, resp, errorList);
return;
}
so = _store.getStoredObject(transaction, path);
if (so == null) {
_store.createResource(transaction, path);
// resp.setStatus(WebdavStatus.SC_CREATED);
} else {
// This has already been created, just update the data
if (so.isNullResource()) {
LockedObject nullResourceLo = _resourceLocks
.getLockedObjectByPath(transaction, path);
if (nullResourceLo == null) {
resp
.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
return;
}
String nullResourceLockToken = nullResourceLo
.getID();
String[] lockTokens = getLockIdFromIfHeader(req);
String lockToken = null;
if (lockTokens != null) {
lockToken = lockTokens[0];
} else {
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
return;
}
if (lockToken.equals(nullResourceLockToken)) {
so.setNullResource(false);
so.setFolder(false);
String[] nullResourceLockOwners = nullResourceLo
.getOwner();
String owner = null;
if (nullResourceLockOwners != null)
owner = nullResourceLockOwners[0];
if (!_resourceLocks.unlock(transaction,
lockToken, owner)) {
resp
.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
errorList.put(path, WebdavStatus.SC_LOCKED);
sendReport(req, resp, errorList);
}
}
}
// User-Agent workarounds
doUserAgentWorkaround(resp);
// setting resourceContent
long resourceLength = _store
.setResourceContent(transaction, path, req
.getInputStream(), null, null);
so = _store.getStoredObject(transaction, path);
if (resourceLength > 0)
so.setResourceLength(resourceLength);
// Now lets report back what was actually saved
} catch (AccessDeniedException e) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
} catch (WebdavException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction,
path, tempLockOwner);
}
} else {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
}
} else {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
}
}
/**
* @param resp
*/
private void doUserAgentWorkaround(HttpServletResponse resp) {
if (_userAgent != null && _userAgent.indexOf("WebDAVFS") != -1
&& _userAgent.indexOf("Transmit") == -1) {
LOG.trace("DoPut.execute() : do workaround for user agent '"
+ _userAgent + "'");
resp.setStatus(WebdavStatus.SC_CREATED);
} else if (_userAgent != null && _userAgent.indexOf("Transmit") != -1) {
// Transmit also uses WEBDAVFS 1.x.x but crashes
// with SC_CREATED response
LOG.trace("DoPut.execute() : do workaround for user agent '"
+ _userAgent + "'");
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
} else {
resp.setStatus(WebdavStatus.SC_CREATED);
}
}
}

View File

@ -0,0 +1,98 @@
package net.sf.webdav.methods;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.webdav.StoredObject;
import net.sf.webdav.ITransaction;
import net.sf.webdav.WebdavStatus;
import net.sf.webdav.IWebdavStore;
import net.sf.webdav.exceptions.LockFailedException;
import net.sf.webdav.locking.IResourceLocks;
import net.sf.webdav.locking.LockedObject;
public class DoUnlock extends DeterminableMethod {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(DoUnlock.class);
private IWebdavStore _store;
private IResourceLocks _resourceLocks;
private boolean _readOnly;
public DoUnlock(IWebdavStore store, IResourceLocks resourceLocks,
boolean readOnly) {
_store = store;
_resourceLocks = resourceLocks;
_readOnly = readOnly;
}
public void execute(ITransaction transaction, HttpServletRequest req,
HttpServletResponse resp) throws IOException, LockFailedException {
LOG.trace("-- " + this.getClass().getName());
if (_readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
} else {
String path = getRelativePath(req);
String tempLockOwner = "doUnlock" + System.currentTimeMillis()
+ req.toString();
try {
if (_resourceLocks.lock(transaction, path, tempLockOwner,
false, 0, TEMP_TIMEOUT, TEMPORARY)) {
String lockId = getLockIdFromLockTokenHeader(req);
LockedObject lo;
if (lockId != null
&& ((lo = _resourceLocks.getLockedObjectByID(
transaction, lockId)) != null)) {
String[] owners = lo.getOwner();
String owner = null;
if (lo.isShared()) {
// more than one owner is possible
if (owners != null) {
for (int i = 0; i < owners.length; i++) {
// remove owner from LockedObject
lo.removeLockedObjectOwner(owners[i]);
}
}
} else {
// exclusive, only one lock owner
if (owners != null)
owner = owners[0];
else
owner = null;
}
if (_resourceLocks.unlock(transaction, lockId, owner)) {
StoredObject so = _store.getStoredObject(
transaction, path);
if (so.isNullResource()) {
_store.removeObject(transaction, path);
}
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
} else {
LOG.trace("DoUnlock failure at " + lo.getPath());
resp.sendError(WebdavStatus.SC_METHOD_FAILURE);
}
} else {
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
}
}
} catch (LockFailedException e) {
e.printStackTrace();
} finally {
_resourceLocks.unlockTemporaryLockedObjects(transaction, path,
tempLockOwner);
}
}
}
}

View File

@ -0,0 +1 @@
#logging.level.net.sf.webdav=trace

View File

@ -0,0 +1,13 @@
package com.github.zxbu.webdavteambition;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class WebdavTeambitionApplicationTests {
@Test
void contextLoads() {
}
}