/*
* Copyright (C) 2010-2015, Martin Goellnitz
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301, USA
*/
package jfs.sync.encdav;
import com.github.sardine.DavResource;
import com.github.sardine.Sardine;
import com.github.sardine.SardineFactory;
import com.github.sardine.impl.SardineException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import jfs.conf.JFSConfig;
import jfs.sync.base.AbstractJFSFileProducerFactory;
import jfs.sync.encryption.AbstractEncryptedStorageAccess;
import jfs.sync.encryption.FileInfo;
import jfs.sync.encryption.StorageAccess;
import jfs.sync.util.DavUtils;
import jfs.sync.util.WindowsProxySelector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Storage access with encrypted files, leaving outmeta data file for each directory but still a direct WebDAV backend.
*/
public class EncDavStorageAccess extends AbstractEncryptedStorageAccess implements StorageAccess {
private static final Logger LOG = LoggerFactory.getLogger(EncDavStorageAccess.class);
private final Map<String, List<DavResource>> folderCache = new HashMap<>();
private Sardine sardine = null;
private final String cipherspec;
public EncDavStorageAccess(String cipher, boolean shortenPaths) {
super(shortenPaths);
cipherspec = cipher;
} // EncDavStorageAccess()
private Sardine getSardine() {
if (sardine==null) {
String username = JFSConfig.getInstance().getServerUserName();
String passphrase = JFSConfig.getInstance().getServerPassPhrase();
sardine = SardineFactory.begin(username, passphrase, WindowsProxySelector.getInstance());
LOG.debug("getSardine() webdav client {}", sardine);
} // if
return sardine;
} // getSardine()
@Override
public String getSeparator() {
return "/";
}
private String getUrl(String rootPath, String relativePath) {
String urlSegment = getFileName(relativePath);
try {
urlSegment = URLEncoder.encode(urlSegment, "UTF-8").replace("%2F", getSeparator());
} catch (UnsupportedEncodingException e) {
LOG.error("getUrl() System doesn't know UTF8 ?!?!");
} // try/catch
LOG.debug("getUrl({}) {}{}", relativePath, rootPath, urlSegment);
return rootPath+urlSegment;
} // getUrl()
@Override
public String getCipherSpec() {
return cipherspec;
} // getCipherSpec()
protected List<DavResource> getListing(String rootPath, String relativePath) {
String url = getUrl(rootPath, relativePath);
List<DavResource> listing = Collections.emptyList();
if (folderCache.containsKey(url)) {
listing = folderCache.get(url);
} else {
try {
listing = getSardine().list(url, 1, DavUtils.getCustomDavProperties());
} catch (Exception e) {
LOG.error("getListing()", e);
} // try/catch
} // if
LOG.debug("getListing() {} elements in {}", listing.size(), url);
return listing;
} // getListing()
private String getEncryptedPathElement(DavResource item, int prefixLength) {
String rPath = item.getPath().substring(prefixLength);
if (rPath.endsWith("/")) {
rPath = rPath.substring(0, rPath.length()-1);
} // if
int x = rPath.lastIndexOf('/');
if (x>0) {
rPath = rPath.substring(x+1);
} // if
LOG.debug("getEncryptedPathElement({}) {}", item, rPath);
return rPath;
} // getEncryptedPathElement()
protected DavResource getFile(String rootPath, String relativePath) {
int uriStartIndex = rootPath.indexOf('/', 9);
int prefixLength = rootPath.substring(uriStartIndex).length()+1;
String[] pathAndName = AbstractJFSFileProducerFactory.getPathAndName(relativePath, "/");
String pathElement = pathAndName[1].length()>0 ? getEncryptedFileName(pathAndName[0], pathAndName[1]) : pathAndName[1];
LOG.debug("getFile() {} + {} {}", relativePath, pathAndName[1], pathElement);
List<DavResource> listing = getListing(rootPath, pathAndName[0]);
DavResource result = null;
for (DavResource item : listing) {
String rPath = getEncryptedPathElement(item, prefixLength);
// LOG.debug("getFile() {} '{}'=='{}' ? {}", item, rPath, pathElement, rPath.equals(pathElement));
if (rPath.equals(pathElement)) {
result = item;
} // if
} // for
LOG.debug("getFile() {} - {} / {}: {}", relativePath, pathAndName[0], pathAndName[1], result);
if (result==null) {
LOG.error("getFile() {}", relativePath, new Exception(""));
} // if
return result;
} // getFile()
@Override
public String[] list(String rootPath, String relativePath) {
List<DavResource> items = getListing(rootPath, relativePath);
int uriStartIndex = rootPath.indexOf('/', 9);
int prefixLength = rootPath.substring(uriStartIndex).length()+1;
LOG.debug("list() {} - {} [{}:{}]", rootPath, relativePath, uriStartIndex, prefixLength);
// decrypt
String[] result = new String[items.size()-1];
int i = 1;
while (i<items.size()) {
DavResource item = items.get(i);
LOG.debug("list() {}: {}", relativePath, item);
String rPath = getEncryptedPathElement(item, prefixLength);
String decryptedItem = getDecryptedFileName(relativePath, rPath);
result[(i++)-1] = decryptedItem;
LOG.debug("list() {} -> {}", rPath, decryptedItem);
} // for
// sort out meta data
Collection<String> itemCollection = new HashSet<>();
for (String item : result) {
if (!getMetaDataFileName(relativePath).equals(item)) {
itemCollection.add(item);
} // if
} // for
// repackage as array
result = new String[itemCollection.size()];
i = 0;
for (String item : itemCollection) {
result[i++] = item;
} // for
return result;
} // list()
@Override
public FileInfo getFileInfo(String rootPath, String relativePath) {
FileInfo result = new FileInfo();
String name = getLastPathElement(relativePath, relativePath);
result.setName(name);
result.setPath(rootPath+relativePath);
DavResource resource = getFile(rootPath, relativePath);
result.setCanRead(false);
result.setCanWrite(false);
result.setDirectory(false);
if (resource!=null) {
result.setDirectory(resource.isDirectory());
result.setExists(true);
} else {
result.setExists(false);
} // if
LOG.debug("getFileInfo() {} e[{}] d[{}]", result.getPath(), result.isExists(), result.isDirectory());
if (result.isExists()) {
result.setCanRead(true);
result.setCanWrite(true);
if (!result.isDirectory()) {
result.setModificationDate(DavUtils.getModificationDate(resource));
result.setSize(-1);
} else {
result.setSize(0);
} // if
} else {
LOG.debug("getFileInfo() could not detect file for {}", result.getPath());
} // if
return result;
} // getFileInfo()
/* TODO: above this line needs modification */
@Override
public boolean createDirectory(String rootPath, String relativePath) {
LOG.debug("createDirectory() {}", relativePath);
String url = getUrl(rootPath, relativePath);
try {
getSardine().createDirectory(url);
} catch (Exception e) {
if (e instanceof SardineException) {
SardineException se = (SardineException) e;
LOG.warn("createDirectory({}) status code: {} {}", url, se.getStatusCode(), se.getResponsePhrase());
} // if
LOG.warn("createDirectory()", e);
return false;
} // try/catch
return true;
}
@Override
public boolean setLastModified(String rootPath, String relativePath, long modificationDate) {
boolean success = false;
try {
DavResource resource = getFile(rootPath, relativePath);
String url = getUrl(rootPath, relativePath)+(resource.isDirectory() ? "/" : "");
success = DavUtils.setLastModified(sardine, url, modificationDate);
} catch (Exception e) {
LOG.error("setLastModified()", e);
} // try/catch
return success;
}
@Override
public boolean setReadOnly(String rootPath, String relativePath) {
return false;
}
@Override
public boolean delete(String rootPath, String relativePath) {
try {
DavResource resource = getFile(rootPath, relativePath);
getSardine().delete(getUrl(rootPath, relativePath)+(resource.isDirectory() ? "/" : ""));
} catch (Exception e) {
LOG.warn("delete()", e);
return false;
} // try/catch
return true;
} // delete()
@Override
public InputStream getInputStream(String rootpath, String relativePath) throws IOException {
String url = getUrl(rootpath, relativePath);
return getSardine().get(url);
} // getInputStream()
@Override
public OutputStream getOutputStream(String rootPath, final String relativePath) throws IOException {
LOG.debug("getOutputStream() {}", relativePath);
final String url = getUrl(rootPath, relativePath);
OutputStream result = new com.gc.iotools.stream.os.OutputStreamToInputStream<String>() {
@Override
protected String doRead(InputStream input) throws Exception {
getSardine().put(url, input);
return "";
}
};
return result;
} // getOutputStream()
@Override
public void flush(String rootPath, FileInfo info) {
// Nothing to do in this implementation
} // flush()
} // EnvDavStorageAccess