/** * * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * **/ package lucee.commons.io.res.type.smb; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Random; import jcifs.smb.NtlmPasswordAuthentication; import jcifs.smb.SmbException; import jcifs.smb.SmbFile; import jcifs.smb.SmbFileOutputStream; import lucee.commons.io.IOUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.ResourceProvider; import lucee.commons.io.res.util.ResourceOutputStream; import lucee.commons.io.res.util.ResourceSupport; import lucee.commons.io.res.util.ResourceUtil; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; public class SMBResource extends ResourceSupport implements Resource{ private SMBResourceProvider provider; private String path; private NtlmPasswordAuthentication auth; private SmbFile _smbFile; private SmbFile _smbDir; private SMBResource(SMBResourceProvider provider) { this.provider = provider; } public SMBResource(SMBResourceProvider provider, String path) { this(provider); _init(_stripAuth(path), _extractAuth(path)); } public SMBResource(SMBResourceProvider provider, String path, NtlmPasswordAuthentication auth) { this(provider); _init(path, auth); } public SMBResource(SMBResourceProvider provider, String parent, String child) { this(provider); _init(ResourceUtil.merge(_stripAuth(parent), child), _extractAuth(parent)); } public SMBResource(SMBResourceProvider provider, String parent, String child, NtlmPasswordAuthentication auth) { this(provider); _init(ResourceUtil.merge(_stripAuth(parent), child), auth); } private void _init (String path, NtlmPasswordAuthentication auth ) { //String[] pathName=ResourceUtil.translatePathName(path); this.path = _stripScheme(path); this.auth = auth; } private String _stripScheme(String path) { return path.replace(_scheme(), "/"); } private String _userInfo (String path) { try { //use http scheme just so we can parse the url and get the user info out String schemeless = _stripScheme(path); schemeless = schemeless.replaceFirst("^/", ""); String result = new URL("http://".concat(schemeless)).getUserInfo(); return SMBResourceProvider.unencryptUserInfo(result); } catch (MalformedURLException e) { return ""; } } private static String _userInfo (NtlmPasswordAuthentication auth,boolean addAtSign) { String result = ""; if( auth != null) { if( !StringUtils.isEmpty( auth.getDomain() ) ) { result += auth.getDomain() + ";"; } if( !StringUtils.isEmpty( auth.getUsername() ) ) { result += auth.getUsername() + ":"; } if( !StringUtils.isEmpty( auth.getPassword() ) ) { result += auth.getPassword(); } if( addAtSign && !StringUtils.isEmpty( result ) ) { result += "@"; } } return result; } private NtlmPasswordAuthentication _extractAuth(String path) { return new NtlmPasswordAuthentication( _userInfo(path) ); } private String _stripAuth(String path) { return _calculatePath(path).replaceFirst(_scheme().concat("[^/]*@"),""); } private SmbFile _file() { return _file(false); } private SmbFile _file( boolean expectDirectory ) { String _path = _calculatePath(getInnerPath()); SmbFile result; if(expectDirectory) { if(!_path.endsWith("/")) _path += "/"; if(_smbDir == null) { _smbDir = provider.getFile(_path,auth); } result = _smbDir; } else { if(_smbFile == null) { _smbFile = provider.getFile(_path,auth); } result = _smbFile; } return result; } private String _calculatePath(String path) { return _calculatePath(path,null); } private String _calculatePath(String path, NtlmPasswordAuthentication auth) { if ( !path.startsWith( _scheme() ) ) { if(path.startsWith("/") || path.startsWith("\\")) { path = path.substring(1); } if (auth != null) { path = SMBResourceProvider.encryptUserInfo(_userInfo(auth,false)).concat("@").concat(path); } path = _scheme().concat( path ); } return path; } private String _scheme() { return provider.getScheme().concat("://"); } @Override public boolean isReadable() { SmbFile file = _file(); try { return file != null && file.canRead(); } catch (SmbException e) { return false; } } @Override public boolean isWriteable() { SmbFile file = _file(); if(file == null) return false; try { if(file.canWrite()) return true; } catch (SmbException e1) { return false; } try { if (file.getType() == SmbFile.TYPE_SHARE) { // canWrite() doesn't work on shares. always returns false even if you can truly write, test this by opening a file on the share SmbFile testFile = _getTempFile(file,auth); if (testFile == null) return false; if (testFile.canWrite()) return true; OutputStream os=null; try { os = testFile.getOutputStream(); } catch (IOException e) { return false; } finally { if (os != null) IOUtils.closeQuietly(os); testFile.delete(); } return true; } return file.canWrite(); } catch (SmbException e) { return false; } } private SmbFile _getTempFile(SmbFile directory, NtlmPasswordAuthentication auth) throws SmbException { if (!directory.isDirectory()) return null; Random r = new Random(); SmbFile result = provider.getFile(directory.getCanonicalPath() + "/write-test-file.unknown." + r.nextInt(), auth); if (result.exists()) return _getTempFile(directory,auth); //try again return result; } @Override public void remove(boolean alsoRemoveChildren) throws IOException { if(alsoRemoveChildren)ResourceUtil.removeChildren(this); _delete(); } private void _delete() throws IOException{ provider.lock(this); try { SmbFile file = _file(); if (file == null) throw new IOException("Can't delete [" + getPath() + "], SMB path is invalid or inaccessable"); if (file.isDirectory()) { file = _file(true); } file.delete(); } catch (SmbException e) { throw new IOException(e);// for cfcatch type="java.io.IOException" } finally { provider.unlock(this); } } @Override public boolean exists() { SmbFile file = _file(); try { return file != null && file.exists(); } catch (SmbException e) { return false; } } @Override public String getName() { SmbFile file = _file(); if(file == null) return ""; return file.getName().replaceFirst("/$", ""); //remote trailing slash for directories } @Override public String getParent() { // SmbFile's getParent function seems to return just smb:// no matter what, implement custom getParent Function() String path = getPath().replaceFirst("[\\\\/]+$", ""); int location = Math.max(path.lastIndexOf('/'),path.lastIndexOf('\\')); if (location == -1 || location == 0) return ""; return path.substring(0,location); } @Override public Resource getParentResource() { String p = getParent(); if(p==null) return null; return new SMBResource(provider,_stripAuth(p),auth); } @Override public Resource getRealResource(String realpath) { realpath=ResourceUtil.merge(getInnerPath() +"/", realpath); if(realpath.startsWith("../"))return null; return new SMBResource( provider, _calculatePath(realpath,auth), auth ); } private String getInnerPath() { if(path==null) return "/"; return path; } @Override public String getPath() { return _calculatePath(path,auth); } @Override public boolean isAbsolute() { return _file() != null; } @Override public boolean isDirectory() { SmbFile file = _file(); try { return file != null && _file().isDirectory(); } catch (SmbException e) { return false; } } @Override public boolean isFile() { SmbFile file = _file(); try { return file != null && file.isFile(); } catch (SmbException e) { return false; } } @Override public boolean isHidden() { return _isFlagSet(_file(), SmbFile.ATTR_HIDDEN); } @Override public boolean isArchive() { return _isFlagSet(_file(), SmbFile.ATTR_ARCHIVE); } @Override public boolean isSystem() { return _isFlagSet(_file(), SmbFile.ATTR_SYSTEM); } private boolean _isFlagSet(SmbFile file, int flag) { if (file == null) return false; try { return (file.getAttributes() & flag) == flag; } catch (SmbException e) { return false; } } @Override public long lastModified() { SmbFile file = _file(); if (file == null) return 0; try { return file.lastModified(); } catch (SmbException e) { return 0; } } @Override public long length() { SmbFile file = _file(); if (file == null) return 0; try { return file.length(); } catch (SmbException e) { return 0; } } @Override public Resource[] listResources() { if(isFile()) return null; try { SmbFile dir = _file(true); SmbFile[] files = dir.listFiles(); Resource[] result = new Resource[files.length]; for(int i = 0; i < files.length ; i++) { SmbFile file = files[i]; result[i] = new SMBResource(provider,file.getCanonicalPath(),auth); } return result; } catch (SmbException e) { return new Resource[0]; } } @Override public boolean setLastModified(long time){ SmbFile file = _file(); if (file == null) return false; try { provider.lock(this); file.setLastModified(time); } catch (SmbException e) { return false; } catch (IOException e) { return false; } finally { provider.unlock(this); } return true; } @Override public boolean setWritable(boolean writable) { SmbFile file = _file(); if( file == null) return false; try { setAttribute((short)SmbFile.ATTR_READONLY, !writable); } catch (IOException e1) { return false; } return true; } @Override public boolean setReadable(boolean readable) { return setWritable(!readable); } @Override public void createFile(boolean createParentWhenNotExists) throws IOException { try { ResourceUtil.checkCreateFileOK(this, createParentWhenNotExists); //client.unregisterFTPFile(this); IOUtil.copy(new ByteArrayInputStream(new byte[0]), getOutputStream(), true, true); } catch (SmbException e) { throw new IOException(e); // for cfcatch type="java.io.IOException" } } @Override public void createDirectory(boolean createParentWhenNotExists) throws IOException { SmbFile file= _file(true); if (file == null) throw new IOException("SMBFile is inaccessible"); ResourceUtil.checkCreateDirectoryOK(this, createParentWhenNotExists); try { provider.lock(this); file.mkdir(); } catch (SmbException e) { throw new IOException(e); // for cfcatch type="java.io.IOException" } finally { provider.unlock(this); } } @Override public InputStream getInputStream() throws IOException { try { return _file().getInputStream(); } catch (SmbException e) { throw new IOException(e);// for cfcatch type="java.io.IOException" } } @Override public OutputStream getOutputStream(boolean append) throws IOException { ResourceUtil.checkGetOutputStreamOK(this); try { provider.lock(this); SmbFile file =_file(); OutputStream os = new SmbFileOutputStream(file, append); return IOUtil.toBufferedOutputStream(new ResourceOutputStream(this,os)); } catch (IOException e) { provider.unlock(this); throw new IOException(e);// just in case it is an SmbException too... for cfcatch type="java.io.IOException" } } @Override public ResourceProvider getResourceProvider() { return provider; } @Override public int getMode() { return 0; } @Override public void setMode(int mode) throws IOException { // TODO } @Override public void setHidden(boolean value) throws IOException { setAttribute((short)SmbFile.ATTR_SYSTEM, value); } @Override public void setSystem(boolean value) throws IOException { setAttribute((short)SmbFile.ATTR_SYSTEM, value); } @Override public void setArchive(boolean value) throws IOException { setAttribute((short)SmbFile.ATTR_ARCHIVE, value); } @Override public void setAttribute(short attribute, boolean value) throws IOException { int newAttribute = _lookupAttribute(attribute); SmbFile file = _file(); if (file == null) throw new IOException("SMB File is not valid"); try { provider.lock(this); int atts = file.getAttributes(); if (value) { atts = atts | newAttribute; } else { atts = atts & (~newAttribute); } file.setAttributes(atts); } catch (SmbException e) { throw new IOException(e); // for cfcatch type="java.io.IOException" } finally { provider.unlock(this); } } @Override public void moveTo(Resource dest) throws IOException { try { if(dest instanceof SMBResource) { SMBResource destination = (SMBResource)dest; SmbFile file = _file(); file.renameTo(destination._file()); } else { ResourceUtil.moveTo(this, dest,false); } } catch (SmbException e) { throw new IOException(e); // for cfcatch type="java.io.IOException" } } @Override public boolean getAttribute(short attribute) { try { int newAttribute = _lookupAttribute(attribute); return (_file().getAttributes() & newAttribute) != 0; } catch (SmbException e) { return false; } } public SmbFile getSmbFile() { return _file(); } private int _lookupAttribute(short attribute) { int result = attribute; switch (attribute) { case Resource.ATTRIBUTE_ARCHIVE: result = SmbFile.ATTR_ARCHIVE; break; case Resource.ATTRIBUTE_SYSTEM: result = SmbFile.ATTR_SYSTEM; break; case Resource.ATTRIBUTE_HIDDEN: result = SmbFile.ATTR_HIDDEN; break; } return result; } }