diff --git a/commons-vfs2/pom.xml b/commons-vfs2/pom.xml index 31173bdb6f..56364c179c 100644 --- a/commons-vfs2/pom.xml +++ b/commons-vfs2/pom.xml @@ -105,7 +105,6 @@ org.slf4j slf4j-api - test @@ -135,6 +134,18 @@ jackrabbit-standalone test + + + com.hierynomus + smbj + 0.5.1 + + + + org.slf4j + slf4j-simple + 1.5.11 + 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