org.apache.hadoop
@@ -256,6 +267,15 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/SMB2ProviderTestCase.java
+
+
+
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/Resources.properties b/commons-vfs2/src/main/java/org/apache/commons/vfs2/Resources.properties
index 768c12bc39..3d9d68ad9b 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/Resources.properties
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/Resources.properties
@@ -203,6 +203,13 @@ vfs.provider.temp/missing-share-name.error=Share name missing from UNC file name
vfs.provider.smb/missing-share-name.error=The share name is missing from URI "{0}".
vfs.provider.smb/get-type.error=Could not detemine the type of "{0}".
+# SMB2 Provider
+vfs.provider.smb2/missing-share-name.error=The share name is missing from URI "{0}".
+vfs.provider.smb2/share-path-extraction.error=Share name extraction failed for path "{0}".
+vfs.provider.smb2/connect.error=Could not establish connection to "{0}".
+vfs.provider.smb2/folder-create.error=Could not create folder "{0}".
+vfs.provider.smb2/diskentry-create.error=Could not create a handle for file "{0}".
+
# Zip Provider
vfs.provider.zip/open-zip-file.error=Could not open Zip file "{0}".
vfs.provider.zip/close-zip-file.error=Could not close Zip file "{0}".
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/impl/providers.xml b/commons-vfs2/src/main/java/org/apache/commons/vfs2/impl/providers.xml
index f8d571c848..69fb4ab8e9 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/impl/providers.xml
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/impl/providers.xml
@@ -1,144 +1,125 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/.gitignore b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/.gitignore
new file mode 100644
index 0000000000..6dd38fcbc2
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/.gitignore
@@ -0,0 +1 @@
+/SMB3FileNameParser.java
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2ClientWrapper.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2ClientWrapper.java
new file mode 100644
index 0000000000..c40e461623
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2ClientWrapper.java
@@ -0,0 +1,199 @@
+/*
+ * 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
+ *
+ * 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 org.apache.commons.vfs2.provider.smb2;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.provider.GenericFileName;
+
+import com.hierynomus.msdtyp.AccessMask;
+import com.hierynomus.msfscc.FileAttributes;
+import com.hierynomus.msfscc.fileinformation.FileAllInformation;
+import com.hierynomus.msfscc.fileinformation.FileIdBothDirectoryInformation;
+import com.hierynomus.mssmb2.SMB2CreateDisposition;
+import com.hierynomus.mssmb2.SMB2CreateOptions;
+import com.hierynomus.mssmb2.SMB2ShareAccess;
+import com.hierynomus.smbj.SMBClient;
+import com.hierynomus.smbj.SmbConfig;
+import com.hierynomus.smbj.auth.AuthenticationContext;
+import com.hierynomus.smbj.connection.Connection;
+import com.hierynomus.smbj.session.Session;
+import com.hierynomus.smbj.share.DiskEntry;
+import com.hierynomus.smbj.share.DiskShare;
+
+/**
+ * A wrapper to the SMBClient for bundling the related client & connection instances.
+ *
+ * The SMBClient ALWAYS needs a share. The share is part of the rootURI provided by the FileNameParser
+ */
+public class SMB2ClientWrapper extends SMBClient
+{
+ private static final SmbConfig CONFIG = SmbConfig.builder()
+ .withDfsEnabled(true)
+ .withMultiProtocolNegotiate(true)
+ .build();
+
+ protected final FileSystemOptions fileSystemOptions;
+ private final GenericFileName root;
+ private SMBClient smbClient;
+ private Connection connection;
+ private Session session;
+ private DiskShare diskShare;
+
+ protected SMB2ClientWrapper(final GenericFileName root, final FileSystemOptions fileSystemOptions) throws FileSystemException
+ {
+ this.root = root;
+ this.fileSystemOptions = fileSystemOptions;
+ smbClient = new SMBClient(CONFIG);
+ setupClient();
+ }
+
+ private void setupClient() throws FileSystemException
+ {
+ final GenericFileName rootName = getRoot();
+
+ //the relevant data to authenticate a connection
+ String userName = (rootName.getUserName().equals("") || rootName.getUserName() == null) ? "" : ((rootName.getUserName().contains(";") ? rootName.getUserName().substring(rootName.getUserName().indexOf(";")+1, rootName.getUserName().length()) : rootName.getUserName()));
+ String password = rootName.getPassword();
+ String authDomain = (rootName.getUserName().contains(";") ? rootName.getUserName().substring(0, rootName.getUserName().indexOf(";")) : null);
+
+ //if username == "" the client tries to authenticate "anonymously". It's also possible to submit "guets" as username
+ AuthenticationContext authContext = new AuthenticationContext(userName, password.toCharArray(), authDomain);
+
+ //a connection stack is: SMBClient > Connection > Session > DiskShare
+ try
+ {
+ connection = smbClient.connect(rootName.getHostName());
+ session = connection.authenticate(authContext);
+ String share = ((SMB2FileName) rootName).getShareName();
+ diskShare = (DiskShare) session.connectShare(share);
+ } catch (Exception e)
+ {
+ throw new FileSystemException("vfs.provider.smb2/connect.error", rootName.getHostName(), e.getCause());
+ }
+
+ }
+
+ public GenericFileName getRoot()
+ {
+ return root;
+ }
+
+ public FileAllInformation getFileInfo(String relPath)
+ {
+ try
+ {
+ return diskShare.getFileInformation(relPath);
+ }
+ catch(Exception e)
+ {
+ return null;
+ }
+ }
+
+ //create a WRITE handle on the file
+ public DiskEntry getDiskEntryWrite(String path)
+ {
+ DiskEntry diskEntryWrite = diskShare.open(path,
+ EnumSet.of(AccessMask.MAXIMUM_ALLOWED),
+ EnumSet.of(FileAttributes.FILE_ATTRIBUTE_NORMAL),
+ EnumSet.of(SMB2ShareAccess.FILE_SHARE_WRITE),
+ SMB2CreateDisposition.FILE_OPEN_IF,
+ EnumSet.of(SMB2CreateOptions.FILE_NO_COMPRESSION));
+
+ return diskEntryWrite;
+ }
+
+ //creates a folder and immediately closes the handle
+ public void createFolder(String path)
+ {
+ DiskEntry de = getDiskEntryFolderWrite(path);
+ de.close();
+ }
+
+ public DiskEntry getDiskEntryFolderWrite(String path)
+ {
+ DiskEntry de = diskShare.openDirectory(path,
+ EnumSet.of(AccessMask.GENERIC_ALL),
+ EnumSet.of(FileAttributes.FILE_ATTRIBUTE_NORMAL),
+ EnumSet.of(SMB2ShareAccess.FILE_SHARE_READ),
+ SMB2CreateDisposition.FILE_OPEN_IF,
+ EnumSet.of(SMB2CreateOptions.FILE_DIRECTORY_FILE));
+
+ return de;
+ }
+
+
+
+ //creates a READ handle for the file
+ public DiskEntry getDiskEntryRead(String path)
+ {
+ DiskEntry diskEntryRead = diskShare.open(path,
+ EnumSet.of(AccessMask.GENERIC_READ),
+ EnumSet.of(FileAttributes.FILE_ATTRIBUTE_NORMAL),
+ EnumSet.of(SMB2ShareAccess.FILE_SHARE_READ),
+ SMB2CreateDisposition.FILE_OPEN,
+ EnumSet.of(SMB2CreateOptions.FILE_NO_COMPRESSION));
+
+ return diskEntryRead;
+ }
+
+
+ public String[] getChildren(String path)
+ {
+ List children = new ArrayList();
+
+ for(FileIdBothDirectoryInformation file : diskShare.list(path))
+ {
+ String name = file.getFileName();
+ if (name.equals(".") || name.equals("..") || name.equals("./") || name.equals("../"))
+ {
+ continue;
+ }
+ children.add(file.getFileName());
+ }
+ return children.toArray(new String[children.size()]);
+ }
+
+ public void delete(String path)
+ {
+ FileAllInformation info = null;
+ try
+ {
+ info = diskShare.getFileInformation(path);
+ }
+ catch(Exception e)
+ {
+ //file or folder does not exist
+ return;
+ }
+ if(info.getStandardInformation().isDirectory())
+ {
+ diskShare.rmdir(path, true);
+ }
+ else
+ {
+ diskShare.rm(path);
+ }
+ }
+
+
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileName.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileName.java
new file mode 100644
index 0000000000..0f8904c9ed
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileName.java
@@ -0,0 +1,146 @@
+/*
+ * 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
+ *
+ * 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 org.apache.commons.vfs2.provider.smb2;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.provider.GenericFileName;
+
+/**
+ * Using an explicit fileName for SMB2 since the uri must contain a share name.
+ *
+ * The share name belongs to the rootURI, whereas the AbsPath must not contain the share
+ */
+public class SMB2FileName extends GenericFileName
+{
+ private final String shareName;
+ private String rootUri;
+ private String uri;
+
+ protected SMB2FileName(String scheme, String hostName, int port, int defaultPort, String userName, String password,
+ String path, FileType type, String shareName)
+ {
+ super(scheme, hostName, port, defaultPort, userName, password, path, type);
+ this.shareName = shareName;
+ createURI();
+ }
+
+ public String getShareName()
+ {
+ return shareName;
+ }
+
+ @Override
+ public String getFriendlyURI()
+ {
+ return createURI(false, false);
+ }
+
+ @Override
+ public String getURI()
+ {
+ if (uri == null)
+ {
+ uri = createURI();
+ }
+ return uri;
+ }
+
+ protected String createURI()
+ {
+ return createURI(false, true);
+ }
+
+ //the share needs to be inserted since it has been extracted from absPath (getPath())
+ private String createURI(final boolean useAbsolutePath, final boolean usePassword)
+ {
+ StringBuilder sb = new StringBuilder();
+ appendRootUri(sb, usePassword);
+ if(sb.charAt(sb.length() -1 ) != '/')
+ {
+ sb.append('/');
+ }
+ sb.append(shareName);
+
+ if(!(getPath() == null || getPath().equals("/")))
+ {
+ if(!getPath().startsWith("/"))
+ {
+ sb.append('/');
+ }
+ sb.append(getPath());
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public String getRootURI()
+ {
+ if (this.rootUri == null)
+ {
+ String uri = super.getRootURI();
+ this.rootUri = uri + shareName;
+ }
+ return this.rootUri;
+ }
+
+ @Override
+ public FileName getParent()
+ {
+
+ if (this.rootUri == null)
+ {
+ getRootURI();
+ }
+
+ if (getPath().replaceAll("/", "").equals(shareName) || getPath().equals("/") || getPath().equals(""))
+ {
+ return null; //if this method is called from the root name, return null because there is no parent
+ }
+ else
+ {
+ SMB2FileName name = new SMB2FileName(this.getScheme(), this.getHostName(), this.getPort(),
+ this.getDefaultPort(), this.getUserName(), this.getPassword(),
+ getPath().substring(0, getPath().lastIndexOf("/")), this.getType(), shareName);
+ return name;
+ }
+ }
+
+ //inserting the share name since it has been extracted from the absPath
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getScheme());
+ sb.append("://");
+ sb.append(getUserName());
+ sb.append(':');
+ sb.append(getPassword());
+ sb.append('@');
+ sb.append(getHostName());
+ sb.append("/");
+ sb.append(shareName);
+ if (!getPath().startsWith("/"))
+ {
+ sb.append('/');
+ }
+ sb.append(getPath());
+ return sb.toString();
+ }
+
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileNameParser.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileNameParser.java
new file mode 100644
index 0000000000..f33950e076
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileNameParser.java
@@ -0,0 +1,138 @@
+/*
+ * 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
+ *
+ * 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 org.apache.commons.vfs2.provider.smb2;
+
+import java.net.URI;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.provider.HostFileNameParser;
+import org.apache.commons.vfs2.provider.VfsComponentContext;
+
+public class SMB2FileNameParser extends HostFileNameParser {
+
+ private static final SMB2FileNameParser INSTANCE = new SMB2FileNameParser();
+
+ private static final int PORT = 443;
+
+ public SMB2FileNameParser()
+ {
+ super(PORT);
+ }
+
+ public static SMB2FileNameParser getInstance()
+ {
+ return INSTANCE;
+ }
+
+ protected String extractShareName(URI uri) throws FileSystemException
+ {
+ String s = uri.getPath().startsWith("/") ? uri.getPath().substring(1) : uri.getPath();
+ String[] pathParts = s.split("/");
+ String share = pathParts[0];
+ if(share == null || share.equals(""))
+ {
+ throw new FileSystemException("vfs.provider.smb2/missing-share-name.error", uri.toString());
+ }
+
+ return pathParts[0]; //TODO check share given by uri
+ }
+
+ @Override
+ public FileName parseUri(final VfsComponentContext context, final FileName base, final String uri) throws FileSystemException
+ {
+ FileName parsedFileName = super.parseUri(context, base, uri);
+ String share;
+ if(base == null)
+ {
+ share = extractShareName(parseURIString(parsedFileName.toString()));
+ }
+ else
+ {
+ share = extractShareName(parseURIString(base.toString()));
+ }
+
+ StringBuilder sb = new StringBuilder();
+ Authority auth = extractToPath(parsedFileName.toString(), sb);
+
+ String path;
+
+ if(sb.length() == 0 || (sb.length() == 1 && sb.charAt(0) == '/'))
+ {
+ //this must point to the share root
+ path = "/" + share;
+ }
+ else
+ {
+ path = parsedFileName.getPath();
+ }
+
+ String relPathFromShare;
+ try
+ {
+ relPathFromShare = removeShareFromAbsPath(path, share);
+ }
+ catch(Exception e)
+ {
+ throw new FileSystemException("vfs.provider.smb2/share-path-extraction.error", path, e.getCause());
+ }
+
+ SMB2FileName fileName = new SMB2FileName(auth.getScheme(), auth.getHostName(), auth.getPort(), PORT, auth.getUserName(), auth.getPassword(), relPathFromShare, parsedFileName.getType(), share);
+
+
+ return fileName;
+ }
+
+ public URI parseURIString(String uriString) throws FileSystemException
+ {
+ try
+ {
+ return new URI(uriString);
+ }
+ catch (Exception e)
+ {
+ throw new FileSystemException("vfs.provider.url/badly-formed-uri.error", uriString, e.getCause());
+ }
+ }
+
+ public String removeShareFromAbsPath(String path, String shareName) throws Exception
+ {
+ if(shareName == null || shareName.length() == 0)
+ {
+ throw new Exception("No path provided!");
+ }
+
+ String tmp = path.startsWith("/") ? path.substring(1) : path;
+
+ if(!tmp.substring(0, shareName.length()).equals(shareName)) {
+ throw new Exception("Share does not match the provided path!");
+ }
+
+ tmp = tmp.substring(shareName.length());
+
+ if(tmp.equals("") || tmp.equals("/"))
+ {
+ return "";
+ }
+ return tmp;
+ }
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileObject.java
new file mode 100644
index 0000000000..32f8dd07db
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileObject.java
@@ -0,0 +1,395 @@
+/*
+ * 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
+ *
+ * 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 org.apache.commons.vfs2.provider.smb2;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileObject;
+import org.apache.commons.vfs2.provider.UriParser;
+
+import com.hierynomus.msfscc.fileinformation.FileAllInformation;
+import com.hierynomus.smbj.share.DiskEntry;
+import com.hierynomus.smbj.share.File;
+
+/**
+ * Class containing all its handles from the current Instance but NOT all
+ * possible handles to the same file!!
+ *
+ * Closing a stream (Input || Output) does not release the handle for the
+ * current file. The handle itself must be closed! Otherwise the file got locked
+ * up.
+ *
+ * All methos accessing the FileSystem are declared a synchronized for
+ * thread-safetyness
+ */
+public class SMB2FileObject extends AbstractFileObject
+{
+ private final String relPathToShare;
+ private FileAllInformation fileInfo;
+ private FileName rootName;
+ private DiskEntry diskEntryWrite;
+ private DiskEntry diskEntryRead;
+ private DiskEntry diskEntryFolderWrite;
+
+ protected SMB2FileObject(AbstractFileName name, final SMB2FileSystem fs, final FileName rootName)
+ {
+ super(name, fs);
+ String relPath = name.getURI().substring(rootName.getURI().length());
+ // smb shares do not accept "/" --> it needs a "\" which is represented by "\\"
+ relPathToShare = relPath.startsWith("/") ? relPath.substring(1).replace("/", "\\") : relPath.replace("/", "\\");
+ this.rootName = rootName;
+ }
+
+ @Override
+ protected long doGetContentSize() throws Exception
+ {
+ getFileInfo();
+ return fileInfo.getStandardInformation().getEndOfFile();
+ }
+
+ @Override
+ protected InputStream doGetInputStream() throws Exception
+ {
+ if (!getType().hasContent())
+ {
+ throw new FileSystemException("vfs.provider/read-not-file.error", getName());
+ }
+ if (diskEntryRead == null)
+ {
+ getDiskEntryRead();
+ }
+ InputStream is = ((File) diskEntryRead).getInputStream();
+
+ // wrapped to override the close method. For further details see
+ // SMB3InputStreamWrapper.class
+ SMB2InputStreamWrapper inputStream = new SMB2InputStreamWrapper(is, this);
+ return inputStream;
+ }
+
+ @Override
+ protected FileType doGetType() throws Exception
+ {
+ synchronized (getFileSystem())
+ {
+ if (this.fileInfo == null)
+ {
+ // returns null if the diskShare cannot the file info's. Therefore : imaginary
+ getFileInfo();
+ }
+ if (fileInfo == null)
+ {
+ return FileType.IMAGINARY;
+ } else
+ {
+ return (fileInfo.getStandardInformation().isDirectory()) ? FileType.FOLDER : FileType.FILE;
+ }
+ }
+ }
+
+ private void getFileInfo()
+ {
+ if (fileInfo == null)
+ {
+ synchronized (getFileSystem())
+ {
+ SMB2FileSystem fileSystem = (SMB2FileSystem) getFileSystem();
+ SMB2ClientWrapper client = (SMB2ClientWrapper) fileSystem.getClient();
+ try
+ {
+ fileInfo = client.getFileInfo(getRelPathToShare());
+ }
+ finally
+ {
+ fileSystem.putClient(client);
+ }
+ }
+
+ }
+ }
+
+ @Override
+ protected String[] doListChildren() throws Exception
+ {
+ // not using this method
+ return null;
+ }
+
+ @Override
+ public FileObject getParent() throws FileSystemException
+ {
+ synchronized (getFileSystem())
+ {
+ AbstractFileName name = (AbstractFileName) getName().getParent();
+
+ // root folder has no parent
+ if (name == null)
+ {
+ return null;
+ }
+ FileObject cachedFile = getFileSystem().getFileSystemManager().getFilesCache().getFile(getFileSystem(),
+ name);
+ if (cachedFile != null)
+ {
+ return cachedFile;
+ } else
+ {
+ return new SMB2FileObject(name, (SMB2FileSystem) getFileSystem(), rootName);
+ }
+ }
+ }
+
+ @Override
+ protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception
+ {
+ if (diskEntryWrite == null)
+ {
+ getDiskEntryWrite();
+ }
+
+ return ((File) diskEntryWrite).getOutputStream();
+ }
+
+ @Override
+ protected void doCreateFolder() throws Exception
+ {
+ try
+ {
+ synchronized (getFileSystem())
+ {
+ SMB2FileSystem fileSystem = (SMB2FileSystem) getFileSystem();
+ SMB2ClientWrapper client = (SMB2ClientWrapper) fileSystem.getClient();
+ try
+ {
+ client.createFolder(getRelPathToShare());
+ }
+ finally
+ {
+ fileSystem.putClient(client);
+ }
+ }
+ } catch (Exception e)
+ {
+ throw new FileSystemException("vfs.provider.smb2/folder-create.error", getName(), e.getCause());
+ }
+ }
+
+ @Override
+ protected void endOutput() throws Exception
+ {
+ super.endOutput();
+ closeAllHandles(); // also close the handles
+ }
+
+ private void getDiskEntryWrite() throws Exception
+ {
+ closeAllHandles();
+ try
+ {
+ synchronized (getFileSystem())
+ {
+ SMB2FileSystem fileSystem = (SMB2FileSystem) getFileSystem();
+ diskEntryWrite = fileSystem.getDiskEntryWrite(getRelPathToShare());
+ }
+ } catch (Exception e)
+ {
+ throw new FileSystemException("vfs.provider.smb2/diskentry-create.error", getName(), e.getCause());
+ }
+ }
+
+ private void getDiskEntryRead() throws Exception
+ {
+ try
+ {
+ synchronized (getFileSystem())
+ {
+ SMB2FileSystem fileSystem = (SMB2FileSystem) getFileSystem();
+ diskEntryRead = fileSystem.getDiskEntryRead(getRelPathToShare());
+ }
+ } catch (Exception e)
+ {
+ throw new FileSystemException("vfs.provider.smb2/diskentry-create.error", getName(), e.getCause());
+ }
+ }
+
+ private void getDiskEntryFolderWrite() throws Exception
+ {
+ try
+ {
+ synchronized (getFileSystem())
+ {
+ SMB2FileSystem fileSystem = (SMB2FileSystem) getFileSystem();
+ diskEntryFolderWrite = fileSystem.getDiskEntryFolderWrite(getRelPathToShare());
+ }
+ } catch (Exception e)
+ {
+ throw new FileSystemException("vfs.provider.smb2/diskentry-create.error", getName(), e.getCause());
+ }
+ }
+
+ public String getRelPathToShare()
+ {
+ return decodeOrGet(relPathToShare);
+ }
+
+ public String decodeOrGet(String s)
+ {
+ try
+ {
+ return UriParser.decode(s);
+ } catch (FileSystemException e)
+ {
+ return s;
+ }
+ }
+
+ public String encodeOrGet(String s)
+ {
+ return UriParser.encode(s);
+ }
+
+ @Override
+ protected void doRename(final FileObject newFile) throws Exception
+ {
+ if (doGetType() == FileType.FOLDER)
+ {
+ if (diskEntryFolderWrite == null)
+ {
+ getDiskEntryFolderWrite();
+ }
+ SMB2FileObject fo = (SMB2FileObject) newFile;
+ diskEntryFolderWrite.rename(fo.getRelPathToShare());
+ } else
+ {
+ if (diskEntryWrite == null)
+ {
+ getDiskEntryWrite();
+ }
+ SMB2FileObject fo = (SMB2FileObject) newFile;
+ diskEntryWrite.rename(fo.getRelPathToShare());
+
+ // TODO maybo obsoloete
+ closeAllHandles();
+ }
+ }
+
+ @Override
+ protected FileObject[] doListChildrenResolved() throws Exception
+ {
+
+ synchronized (getFileSystem())
+ {
+ if (getType() != FileType.FOLDER)
+ {
+ throw new FileSystemException("vfs.provider/list-children-not-folder.error", this);
+ }
+
+ List children = new ArrayList();
+
+ SMB2FileSystem fileSystem = (SMB2FileSystem) getFileSystem();
+ SMB2ClientWrapper client = (SMB2ClientWrapper) fileSystem.getClient();
+ String[] childrenNames;
+ try
+ {
+ childrenNames = client.getChildren(getRelPathToShare());
+ }
+ finally
+ {
+ fileSystem.putClient(client);
+ }
+
+ for (int i = 0; i < childrenNames.length; i++)
+ {
+ children.add(fileSystem.getFileSystemManager().resolveFile(this, encodeOrGet(childrenNames[i])));
+ }
+ return children.toArray(new FileObject[children.size()]);
+ }
+ }
+
+ @Override
+ protected void doDelete() throws Exception
+ {
+ synchronized (getFileSystem())
+ {
+ if (diskEntryRead != null)
+ {
+ diskEntryRead.close();
+ }
+ endOutput();
+
+ SMB2FileSystem fileSystem = (SMB2FileSystem) getFileSystem();
+ SMB2ClientWrapper client = (SMB2ClientWrapper) fileSystem.getClient();
+ try
+ {
+ client.delete(getRelPathToShare());
+ }
+ finally
+ {
+ fileSystem.putClient(client);
+ }
+ }
+ }
+
+ @Override
+ protected long doGetLastModifiedTime() throws Exception
+ {
+ getFileInfo();
+ return fileInfo.getBasicInformation().getChangeTime().getWindowsTimeStamp();
+ }
+
+ // needs to be overridden to also close the file Handles when the FileObject is
+ // closed. Otherwise files got locked up
+ @Override
+ public void close() throws FileSystemException
+ {
+ super.close();
+ closeAllHandles();
+ }
+
+ private void closeAllHandles()
+ {
+ if (diskEntryRead != null)
+ {
+ diskEntryRead.close();
+ diskEntryRead = null;
+ }
+ if (diskEntryWrite != null)
+ {
+ diskEntryWrite.close();
+ diskEntryWrite = null;
+ }
+ }
+
+ @Override
+ protected void doDetach()
+ {
+ this.fileInfo = null;
+ }
+
+ public String toString()
+ {
+ return getName().toString();
+ }
+
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileProvider.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileProvider.java
new file mode 100644
index 0000000000..c9069ffc68
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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
+ *
+ * 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 org.apache.commons.vfs2.provider.smb2;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.commons.vfs2.Capability;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileSystem;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.UserAuthenticationData;
+import org.apache.commons.vfs2.provider.AbstractOriginatingFileProvider;
+import org.apache.commons.vfs2.provider.GenericFileName;
+
+public class SMB2FileProvider extends AbstractOriginatingFileProvider
+{
+
+ /**
+ * Authenticator types.
+ */
+ public static final UserAuthenticationData.Type[] AUTHENTICATOR_TYPES = new UserAuthenticationData.Type[]
+ { UserAuthenticationData.USERNAME, UserAuthenticationData.PASSWORD };
+
+ static final Collection capabilities = Collections.unmodifiableCollection(Arrays.asList(new Capability[]
+ { Capability.CREATE, Capability.DELETE, Capability.RENAME, Capability.GET_TYPE, Capability.LIST_CHILDREN,
+ Capability.READ_CONTENT, Capability.GET_LAST_MODIFIED, Capability.URI, Capability.WRITE_CONTENT }));
+
+ public SMB2FileProvider()
+ {
+ super();
+ setFileNameParser(SMB2FileNameParser.getInstance());
+ }
+
+ @Override
+ public Collection getCapabilities()
+ {
+ return capabilities;
+ }
+
+ @Override
+ protected FileSystem doCreateFileSystem(FileName name, FileSystemOptions fileSystemOptions)
+ throws FileSystemException
+ {
+ final GenericFileName rootName = (GenericFileName) name;
+ final SMB2ClientWrapper smbClient = new SMB2ClientWrapper(rootName, fileSystemOptions);
+ return new SMB2FileSystem(rootName, fileSystemOptions, smbClient);
+ }
+
+ @Override
+ public FileName parseUri(final FileName base, final String uri) throws FileSystemException
+ {
+ if (getFileNameParser() != null)
+ {
+ //if (uri.endsWith("//")) // TODO really parse if share is not in uri
+ //{
+ //return ((SMB2FileNameParser) getFileNameParser()).parseShareRoot(getContext(), base, uri);
+ return getFileNameParser().parseUri(getContext(), base, uri);
+ //}
+ //return getFileNameParser().parseUri(getContext(), base, uri);
+ }
+ throw new FileSystemException("vfs.provider/filename-parser-missing.error");
+ }
+
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileSystem.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileSystem.java
new file mode 100644
index 0000000000..3cdac6f618
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2FileSystem.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ *
+ * 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 org.apache.commons.vfs2.provider.smb2;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.commons.vfs2.Capability;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileSystem;
+
+import com.hierynomus.smbj.SMBClient;
+import com.hierynomus.smbj.share.DiskEntry;
+
+public class SMB2FileSystem extends AbstractFileSystem
+{
+ private final AtomicReference client = new AtomicReference();
+
+ protected SMB2FileSystem(FileName rootName, FileSystemOptions fileSystemOptions, SMBClient smbClient)
+ {
+ super(rootName, null, fileSystemOptions);
+ client.set(smbClient);
+ }
+
+ @Override
+ protected FileObject createFile(AbstractFileName name) throws Exception
+ {
+ return new SMB2FileObject(name, this, getRootName());
+ }
+
+ @Override
+ protected void addCapabilities(Collection caps)
+ {
+ caps.addAll(SMB2FileProvider.capabilities);
+ }
+
+ public SMBClient getClient()
+ {
+ return (SMB2ClientWrapper) client.getAndSet(null);
+ }
+
+ public void putClient(SMBClient smbClient)
+ {
+ client.set(smbClient);
+ }
+
+ public DiskEntry getDiskEntryWrite(String path)
+ {
+ return ((SMB2ClientWrapper) client.get()).getDiskEntryWrite(path);
+ }
+
+ public DiskEntry getDiskEntryRead(String path)
+ {
+ return ((SMB2ClientWrapper) client.get()).getDiskEntryRead(path);
+ }
+
+ public DiskEntry getDiskEntryFolderWrite(String path)
+ {
+ return ((SMB2ClientWrapper) client.get()).getDiskEntryFolderWrite(path);
+ }
+
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2InputStreamWrapper.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2InputStreamWrapper.java
new file mode 100644
index 0000000000..9b396becfd
--- /dev/null
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/smb2/SMB2InputStreamWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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
+ *
+ * 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 org.apache.commons.vfs2.provider.smb2;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.vfs2.FileObject;
+
+public class SMB2InputStreamWrapper extends InputStream
+{
+ private InputStream is;
+ private final FileObject fo;
+
+ public SMB2InputStreamWrapper(InputStream is, final FileObject fo)
+ {
+ this.is = is;
+ this.fo = fo;
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ return is.read();
+ }
+
+ //the only reason this class exists
+ @Override
+ public void close() throws IOException
+ {
+ is.close();
+ fo.close();
+ }
+}
diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/smb2/test/SMB2ProviderTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/smb2/test/SMB2ProviderTestCase.java
new file mode 100644
index 0000000000..8ace331d07
--- /dev/null
+++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/smb2/test/SMB2ProviderTestCase.java
@@ -0,0 +1,47 @@
+package org.apache.commons.vfs2.provider.smb2.test;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
+import org.apache.commons.vfs2.provider.smb2.SMB2FileProvider;
+import org.apache.commons.vfs2.test.AbstractProviderTestConfig;
+import org.apache.commons.vfs2.test.ProviderTestConfig;
+import org.apache.commons.vfs2.test.ProviderTestSuite;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class SMB2ProviderTestCase extends AbstractProviderTestConfig implements ProviderTestConfig
+{
+
+ private final static String HOSTNAME = "127.0.0.1";
+ private final static String USERINFO = "testuser:password";
+ private final static String SHARENAME = "share";
+ private final static int PORT = 443;
+
+ final TestSuite suite = new TestSuite();
+
+ public static Test suite() throws Exception
+ {
+ return (Test) new ProviderTestSuite(new SMB2ProviderTestCase());
+ }
+
+ @Override
+ public void prepare(final DefaultFileSystemManager manager) throws Exception
+ {
+ manager.addProvider("smb2", new SMB2FileProvider());
+ }
+
+ public FileObject getBaseTestFolder(final FileSystemManager manager) throws Exception
+ {
+ return manager.resolveFile(buildURI().toString());
+ }
+
+ private URI buildURI() throws URISyntaxException
+ {
+ return new URI("smb2", USERINFO, HOSTNAME, PORT, ("/" + SHARENAME), null, null);
+ }
+}
\ No newline at end of file