/* jcifs smb client library in Java * Copyright (C) 2000 "Michael B. Allen" * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package jcifs.smb; import java.net.URLConnection; import java.net.URL; import java.net.MalformedURLException; import java.net.UnknownHostException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import jcifs.Config; import jcifs.util.LogStream; import jcifs.UniAddress; import jcifs.netbios.NbtAddress; import java.util.Date; /** * This class represents a resource on an SMB network. Mainly these * resources are files and directories however an SmbFile * may also refer to servers and workgroups. If the resource is a file or * directory the methods of SmbFile follow the behavior of * the well known {@link java.io.File} class. One fundamental difference * is the usage of a URL scheme [1] to specify the target file or * directory. SmbFile URLs have the following syntax: * *
 *     smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?[param=value[param2=value2[...]]]
 * 
* * This example: * *
 *     smb://storage15/public/foo.txt
 * 
* * would reference the file foo.txt in the share * public on the server storage15. In addition * to referencing files and directories, jCIFS can also address servers, * and workgroups. *

* Important: all SMB URLs that represent * workgroups, servers, shares, or directories require a trailing slash '/'. * *

* When using the java.net.URL class with * 'smb://' URLs it is necessary to first call the static * jcifs.Config.registerSmbURLHandler(); method. This is required * to register the SMB protocol handler. *

* The userinfo component of the SMB URL (domain;user:pass) must * be URL encoded if it contains reserved characters. According to RFC 2396 * these characters are non US-ASCII characters and most meta characters * however jCIFS will work correctly with anything but '@' which is used * to delimit the userinfo component from the server and '%' which is the * URL escape character itself. *

* The server * component may a traditional NetBIOS name, a DNS name, or IP * address. These name resolution mechanisms and their resolution order * can be changed (See Setting Name * Resolution Properties). The servername and path components are * not case sensitive but the domain, username, and password components * are. It is also likely that properties must be specified for jcifs * to function (See Setting * JCIFS Properties). Here are some examples of SMB URLs with brief * descriptions of what they do: * *

[1] This URL scheme is based largely on the SMB * Filesharing URL Scheme IETF draft. * *

* * * * * * * * * * * * * * * * * * * * * * * * * * *
SMB URL Examples
URLDescription
smb://users-nyc;miallen:mypass@angus/tmp/ * This URL references a share called tmp on the server * angus as user miallen who's password is * mypass. *
* smb://Administrator:P%40ss@msmith1/c/WINDOWS/Desktop/foo.txt * A relativly sophisticated example that references a file * msmith1's desktop as user Administrator. Notice the '@' is URL encoded with the '%40' hexcode escape. *
smb://angus/ * This references only a server. The behavior of some methods is different * in this context(e.g. you cannot delete a server) however * as you might expect the list method will list the available * shares on this server. *
smb://myworkgroup/ * This syntactically is identical to the above example. However if * myworkgroup happends to be a workgroup(which is indeed * suggested by the name) the list method will return * a list of servers that have registered themselves as members of * myworkgroup. *
smb:// * Just as smb://server/ lists shares and * smb://workgroup/ lists servers, the smb:// * URL lists all available workgroups on a netbios LAN. Again, * in this context many methods are not valid and return default * values(e.g. isHidden and renameTo will always * return false). *
smb://angus.foo.net/d/jcifs/pipes.doc * The server name may also be a DNS name as it is in this example. See * Setting Name Resolution Properties * for details. *
smb://192.168.1.15/ADMIN$/ * The server name may also be an IP address. See Setting Name Resolution Properties * for details. *
* smb://domain;username:password@server/share/path/to/file.txt * A prototypical example that uses all the fields. *
smb://myworkgroup/angus/ <-- ILLEGAL * Despite the hierarchial relationship between workgroups, servers, and * filesystems this example is not valid. *
* smb://server/share/path/to/dir <-- ILLEGAL * URLs that represent workgroups, servers, shares, or directories require a trailing slash '/'. *
* smb://MYGROUP/?SERVER=192.168.10.15 * SMB URLs support some query string parameters. In this example * the SERVER parameter is used to override the * server name service lookup to contact the server 192.168.10.15 * (presumably known to be a master * browser) for the server list in workgroup MYGROUP. *
* *

A second constructor argument may be specified to augment the URL * for better programmatic control when processing many files under * a common base. This is slightly different from the corresponding * java.io.File usage; a '/' at the beginning of the second * parameter will still use the server component of the first parameter. The * examples below illustrate the resulting URLs when this second contructor * argument is used. * *

* * * * * * * * * * * * * * * * * * * * * * *
* Examples Of SMB URLs When Augmented With A Second Constructor Parameter
* First ParameterSecond ParameterResult
* smb://host/share/a/b/ * * c/d/ * * smb://host/share/a/b/c/d/ *
* smb://host/share/foo/bar/ * * /share2/zig/zag * * smb://host/share2/zig/zag *
* smb://host/share/foo/bar/ * * ../zip/ * * smb://host/share/foo/zip/ *
* smb://host/share/zig/zag * * smb://foo/bar/ * * smb://foo/bar/ *
* smb://host/share/foo/ * * ../.././.././../foo/ * * smb://host/foo/ *
* smb://host/share/zig/zag * * / * * smb://host/ *
* smb://server/ * * ../ * * smb://server/ *
* smb:// * * myworkgroup/ * * smb://myworkgroup/ *
* smb://myworkgroup/ * * angus/ * * smb://myworkgroup/angus/ <-- ILLEGAL
(But if you first create an SmbFile with 'smb://workgroup/' and use and use it as the first parameter to a constructor that accepts it with a second String parameter jCIFS will factor out the 'workgroup'.) *
* *

Instances of the SmbFile class are immutable; that is, * once created, the abstract pathname represented by an SmbFile object * will never change. * * @see java.io.File */ public class SmbFile extends URLConnection { private static final String DEFAULT_DOMAIN = Config.getProperty("jcifs.smb.client.domain"); // these are shifted for use in flags static final int O_RDONLY = 0x010000; static final int O_WRONLY = 0x020000; static final int O_RDWR = 0x030000; static final int O_APPEND = 0x040000; // share access /** * When specified as the shareAccess constructor parameter, * other SMB clients (including other threads making calls into jCIFS) * will not be permitted to access the target file and will receive "The * file is being accessed by another process" message. */ public static final int FILE_NO_SHARE = 0x00; /** * When specified as the shareAccess constructor parameter, * other SMB clients will be permitted to read from the target file while * this file is open. This constant may be logically OR'd with other share * access flags. */ public static final int FILE_SHARE_READ = 0x01; /** * When specified as the shareAccess constructor parameter, * other SMB clients will be permitted to write to the target file while * this file is open. This constant may be logically OR'd with other share * access flags. */ public static final int FILE_SHARE_WRITE = 0x02; /** * When specified as the shareAccess constructor parameter, * other SMB clients will be permitted to delete the target file while * this file is open. This constant may be logically OR'd with other share * access flags. */ public static final int FILE_SHARE_DELETE = 0x04; // Open Function Encoding // create if the file does not exist static final int O_CREAT = 0x0010; // fail if the file exists static final int O_EXCL = 0x0001; // truncate if the file exists static final int O_TRUNC = 0x0002; // file attribute encoding /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() will be read-only */ public static final int ATTR_READONLY = 0x01; /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() will be hidden */ public static final int ATTR_HIDDEN = 0x02; /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() will be a system file */ public static final int ATTR_SYSTEM = 0x04; /** * A file with this bit on as returned by getAttributes() is * a volume */ public static final int ATTR_VOLUME = 0x08; /** * A file with this bit on as returned by getAttributes() is * a directory */ public static final int ATTR_DIRECTORY = 0x10; /** * A file with this bit on as returned by getAttributes() or set * with setAttributes() is an archived file */ public static final int ATTR_ARCHIVE = 0x20; // extended file attribute encoding(others same as above) static final int ATTR_COMPRESSED = 0x800; static final int ATTR_NORMAL = 0x080; static final int ATTR_TEMPORARY = 0x100; static final int ATTR_GET_MASK = 0x3F; static final int ATTR_SET_MASK = 0x27; static final int DEFAULT_ATTR_EXPIRATION_PERIOD = 5000; static final int HASH_DOT = ".".hashCode(); static final int HASH_DOT_DOT = "..".hashCode(); static LogStream log = LogStream.getInstance(); static long attrExpirationPeriod; static { try { Class.forName( "jcifs.Config" ); } catch( ClassNotFoundException cnfe ) { cnfe.printStackTrace(); } attrExpirationPeriod = Config.getLong( "jcifs.smb.client.attrExpirationPeriod", DEFAULT_ATTR_EXPIRATION_PERIOD ); } /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a regular file or directory. */ public static final int TYPE_FILESYSTEM = 0x01; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a workgroup. */ public static final int TYPE_WORKGROUP = 0x02; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a server. */ public static final int TYPE_SERVER = 0x04; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a share. */ public static final int TYPE_SHARE = 0x08; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a named pipe. */ public static final int TYPE_NAMED_PIPE = 0x10; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a printer. */ public static final int TYPE_PRINTER = 0x20; /** * Returned by {@link #getType()} if the resource this SmbFile * represents is a communications device. */ public static final int TYPE_COMM = 0x40; private String canon; // Initially null; set by getUncPath; dir must end with '/' private String share; // Can be null private long createTime; private long lastModified; private int attributes; private long attrExpiration; private long size; private long sizeExpiration; private NtlmPasswordAuthentication auth; // Cannot be null private boolean isExists; private int shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; private SmbComBlankResponse blank_resp = null; private DfsReferral dfsReferral = null; // Only used by getDfsPath() SmbTree tree = null; // Initially null; may be !tree.treeConnected String unc; // Initially null; set by getUncPath; never ends with '/' int fid; // Initially 0; set by open() int type; boolean opened; /** * Constructs an SmbFile representing a resource on an SMB network such as * a file or directory. See the description and examples of smb URLs above. * * @param url A URL string * @throws MalformedURLException * If the parent and child parameters * do not follow the prescribed syntax */ public SmbFile( String url ) throws MalformedURLException { this( new URL( null, url, Handler.SMB_HANDLER )); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory. The second parameter is a relative path from * the parent SmbFile. See the description above for examples * of using the second name parameter. * * @param context A base SmbFile * @param name A path string relative to the parent paremeter * @throws MalformedURLException * If the parent and child parameters * do not follow the prescribed syntax * @throws UnknownHostException * If the server or workgroup of the context file cannot be determined */ public SmbFile( SmbFile context, String name ) throws MalformedURLException, UnknownHostException { this( context.isWorkgroup0() ? new URL( null, "smb://" + name, Handler.SMB_HANDLER ) : new URL( context.url, name, Handler.SMB_HANDLER ), context.auth ); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory. The second parameter is a relative path from * the parent. See the description above for examples of * using the second chile parameter. * * @param context A URL string * @param name A path string relative to the context paremeter * @throws MalformedURLException * If the context and name parameters * do not follow the prescribed syntax */ public SmbFile( String context, String name ) throws MalformedURLException { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER )); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory. * * @param url A URL string * @param auth The credentials the client should use for authentication * @throws MalformedURLException * If the url parameter does not follow the prescribed syntax */ public SmbFile( String url, NtlmPasswordAuthentication auth ) throws MalformedURLException { this( new URL( null, url, Handler.SMB_HANDLER ), auth ); } /** * Constructs an SmbFile representing a file on an SMB network. The * shareAccess parameter controls what permissions other * clients have when trying to access the same file while this instance * is still open. This value is either FILE_NO_SHARE or any * combination of FILE_SHARE_READ, FILE_SHARE_WRITE, * and FILE_SHARE_DELETE logically OR'd together. * * @param url A URL string * @param auth The credentials the client should use for authentication * @param shareAccess Specifies what access other clients have while this file is open. * @throws MalformedURLException * If the url parameter does not follow the prescribed syntax */ public SmbFile( String url, NtlmPasswordAuthentication auth, int shareAccess ) throws MalformedURLException { this( new URL( null, url, Handler.SMB_HANDLER ), auth ); if ((shareAccess & ~(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)) != 0) { throw new RuntimeException( "Illegal shareAccess parameter" ); } this.shareAccess = shareAccess; } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory. The second parameter is a relative path from * the context. See the description above for examples of * using the second name parameter. * * @param context A URL string * @param name A path string relative to the context paremeter * @param auth The credentials the client should use for authentication * @throws MalformedURLException * If the context and name parameters * do not follow the prescribed syntax */ public SmbFile( String context, String name, NtlmPasswordAuthentication auth ) throws MalformedURLException { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER ), auth ); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory. The second parameter is a relative path from * the context. See the description above for examples of * using the second name parameter. The shareAccess * parameter controls what permissions other clients have when trying * to access the same file while this instance is still open. This * value is either FILE_NO_SHARE or any combination * of FILE_SHARE_READ, FILE_SHARE_WRITE, and * FILE_SHARE_DELETE logically OR'd together. * * @param context A URL string * @param name A path string relative to the context paremeter * @param auth The credentials the client should use for authentication * @param shareAccess Specifies what access other clients have while this file is open. * @throws MalformedURLException * If the context and name parameters * do not follow the prescribed syntax */ public SmbFile( String context, String name, NtlmPasswordAuthentication auth, int shareAccess ) throws MalformedURLException { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER ), auth ); if ((shareAccess & ~(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)) != 0) { throw new RuntimeException( "Illegal shareAccess parameter" ); } this.shareAccess = shareAccess; } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory from a URL object. * * @param url The URL of the target resource */ public SmbFile( URL url ) { this( url, new NtlmPasswordAuthentication( url.getUserInfo() )); } /** * Constructs an SmbFile representing a resource on an SMB network such * as a file or directory from a URL object and an * NtlmPasswordAuthentication object. * * @param url The URL of the target resource * @param auth The credentials the client should use for authentication */ public SmbFile( URL url, NtlmPasswordAuthentication auth ) { super( url ); this.auth = auth == null ? new NtlmPasswordAuthentication( url.getUserInfo() ) : auth; getUncPath0(); } SmbFile( SmbFile context, String name, int type, int attributes, long createTime, long lastModified, long size ) throws MalformedURLException, UnknownHostException { this( context.isWorkgroup0() ? new URL( null, "smb://" + name + "/", Handler.SMB_HANDLER ) : new URL( context.url, name + (( attributes & ATTR_DIRECTORY ) > 0 ? "/" : "" ))); /* why was this removed before? DFS? copyTo? Am I going around in circles? */ auth = context.auth; if( context.share != null ) { this.tree = context.tree; } int last = name.length() - 1; if( name.charAt( last ) == '/' ) { name = name.substring( 0, last ); } if( context.share == null ) { this.unc = "\\"; } else if( context.unc.equals( "\\" )) { this.unc = '\\' + name; } else { this.unc = context.unc + '\\' + name; } /* why? am I going around in circles? * this.type = type == TYPE_WORKGROUP ? 0 : type; */ this.type = type; this.attributes = attributes; this.createTime = createTime; this.lastModified = lastModified; this.size = size; isExists = true; attrExpiration = sizeExpiration = System.currentTimeMillis() + attrExpirationPeriod; } private SmbComBlankResponse blank_resp() { if( blank_resp == null ) { blank_resp = new SmbComBlankResponse(); } return blank_resp; } void sendTransaction( SmbComTransaction request, SmbComTransactionResponse response ) throws SmbException { for( ;; ) { connect0(); if( tree.inDfs ) { DfsReferral dr = tree.session.transport.lookupReferral( unc ); if( dr != null ) { UniAddress addr; SmbTransport trans; try { addr = UniAddress.getByName( dr.server ); } catch( UnknownHostException uhe ) { throw new SmbException( dr.server, uhe ); } trans = SmbTransport.getSmbTransport( addr, 0 ); tree = trans.getSmbSession( auth ).getSmbTree( dr.share, null ); unc = dr.nodepath + unc.substring( dr.path.length() ); if( request.path.charAt( request.path.length() - 1 ) == '\\' ) { request.path = unc + '\\'; } else { request.path = unc; } dfsReferral = dr; /* for getDfsPath */ } } if( tree.inDfs ) { request.flags2 |= ServerMessageBlock.FLAGS2_RESOLVE_PATHS_IN_DFS; } else { request.flags2 &= ~ServerMessageBlock.FLAGS2_RESOLVE_PATHS_IN_DFS; } try { tree.sendTransaction( request, response ); break; } catch( DfsReferral dr ) { if( dr.resolveHashes ) { throw dr; } request.reset(); } } } void send( ServerMessageBlock request, ServerMessageBlock response ) throws SmbException { for( ;; ) { connect0(); if( tree.inDfs ) { DfsReferral dr = tree.session.transport.lookupReferral( unc ); if( dr != null ) { UniAddress addr; SmbTransport trans; try { addr = UniAddress.getByName( dr.server ); } catch( UnknownHostException uhe ) { throw new SmbException( dr.server, uhe ); } trans = SmbTransport.getSmbTransport( addr, url.getPort() ); tree = trans.getSmbSession( auth ).getSmbTree( dr.share, null ); unc = request.path = dr.nodepath + unc.substring( dr.path.length() ); dfsReferral = dr; /* for getDfsPath */ } request.flags2 |= ServerMessageBlock.FLAGS2_RESOLVE_PATHS_IN_DFS; } else { request.flags2 &= ~ServerMessageBlock.FLAGS2_RESOLVE_PATHS_IN_DFS; } try { tree.send( request, response ); break; } catch( DfsReferral dr ) { if( dr.resolveHashes ) { throw dr; } } } } static String queryLookup( String query, String param ) { char in[] = query.toCharArray(); int i, ch, st, eq; st = eq = 0; for( i = 0; i < in.length; i++) { ch = in[i]; if( ch == '&' ) { if( eq > st ) { String p = new String( in, st, eq - st ); if( p.equalsIgnoreCase( param )) { eq++; return new String( in, eq, i - eq ); } } st = i + 1; } else if( ch == '=' ) { eq = i; } } if( eq > st ) { String p = new String( in, st, eq - st ); if( p.equalsIgnoreCase( param )) { eq++; return new String( in, eq, in.length - eq ); } } return null; } UniAddress getAddress() throws UnknownHostException { String host = url.getHost(); String path = url.getPath(); String query = url.getQuery(); if( query != null ) { String server = queryLookup( query, "server" ); if( server != null && server.length() > 0 ) { return UniAddress.getByName( server ); } } if( host.length() == 0 ) { try { host = NbtAddress.getByName(NbtAddress.MASTER_BROWSER_NAME, 0x01, null).getHostAddress(); } catch (UnknownHostException ex) { host = DEFAULT_DOMAIN; if (host == null) throw ex; return UniAddress.getByName(host, true); } return UniAddress.getByName(host); } else if( path.length() == 0 || path.equals( "/" )) { return UniAddress.getByName( host, true ); } else { return UniAddress.getByName( host ); } } void connect0() throws SmbException { try { connect(); } catch( UnknownHostException uhe ) { throw new SmbException( "Failed to connect to server", uhe ); } catch( SmbException se ) { throw se; } catch( IOException ioe ) { throw new SmbException( "Failed to connect to server", ioe ); } } /** * It is not necessary to call this method directly. This is the * URLConnection implementation of connect(). */ public void connect() throws IOException { SmbTransport trans; SmbSession ssn; UniAddress addr; if( isConnected() ) { return; } getUncPath0(); addr = getAddress(); trans = SmbTransport.getSmbTransport( addr, url.getPort() ); ssn = trans.getSmbSession( auth ); tree = ssn.getSmbTree( share, null ); try { tree.treeConnect( null, null ); } catch( SmbAuthException sae ) { NtlmPasswordAuthentication a; if( share == null ) { // IPC$ - try "anonymous" credentials ssn = trans.getSmbSession( NtlmPasswordAuthentication.NULL ); tree = ssn.getSmbTree( null, null ); tree.treeConnect( null, null ); } else if(( a = NtlmAuthenticator.requestNtlmPasswordAuthentication( url.toString(), sae )) != null ) { auth = a; ssn = trans.getSmbSession( auth ); tree = ssn.getSmbTree( share, null ); tree.treeConnect( null, null ); } else { throw sae; } } } boolean isConnected() { return (connected = tree != null && tree.treeConnected); } int open0( int flags, int attrs, int options ) throws SmbException { int f; connect0(); if( log.level > 2 ) log.println( "open0: " + unc ); /* * NT Create AndX / Open AndX Request / Response */ if( tree.session.transport.hasCapability( ServerMessageBlock.CAP_NT_SMBS )) { SmbComNTCreateAndXResponse response = new SmbComNTCreateAndXResponse(); send( new SmbComNTCreateAndX( unc, flags, shareAccess, attrs, options, null ), response ); f = response.fid; attributes = response.extFileAttributes & ATTR_GET_MASK; attrExpiration = System.currentTimeMillis() + attrExpirationPeriod; isExists = true; } else { SmbComOpenAndXResponse response = new SmbComOpenAndXResponse(); send( new SmbComOpenAndX( unc, flags, null ), response ); f = response.fid; } return f; } void open( int flags, int attrs, int options ) throws SmbException { if( isOpen() ) { return; } fid = open0( flags, attrs, options ); opened = true; } boolean isOpen() { return opened && isConnected(); } void close( int f, long lastWriteTime ) throws SmbException { if( log.level > 2 ) log.println( "close: " + f ); /* * Close Request / Response */ send( new SmbComClose( f, lastWriteTime ), blank_resp() ); } void close( long lastWriteTime ) throws SmbException { if( isOpen() == false ) { return; } close( fid, lastWriteTime ); opened = false; } void close() throws SmbException { close( 0L ); } /** * Returns the last component of the target URL. This will * effectively be the name of the file or directory represented by this * SmbFile or in the case of URLs that only specify a server * or workgroup, the server or workgroup will be returned. The name of * the root URL smb:// is also smb://. If this * SmbFile refers to a workgroup, server, share, or directory, * the name will include a trailing slash '/' so that composing new * SmbFiles will maintain the trailing slash requirement. * * @return The last component of the URL associated with this SMB * resource or smb:// if the resource is smb:// * itself. */ public String getName() { getUncPath0(); if( canon.length() > 1 ) { int i = canon.length() - 2; while( canon.charAt( i ) != '/' ) { i--; } return canon.substring( i + 1 ); } else if( share != null ) { return share + '/'; } else if( url.getHost().length() > 0 ) { return url.getHost() + '/'; } else { return "smb://"; } } /** * Everything but the last component of the URL representing this SMB * resource is effectivly it's parent. The root URL smb:// * does not have a parent. In this case smb:// is returned. * * @return The parent directory of this SMB resource or * smb:// if the resource refers to the root of the URL * hierarchy which incedentally is also smb://. */ public String getParent() { String str = url.getAuthority(); if( str.length() > 0 ) { StringBuffer sb = new StringBuffer( "smb://" ); sb.append( str ); getUncPath0(); if( canon.length() > 1 ) { sb.append( canon ); } else { sb.append( '/' ); } str = sb.toString(); int i = str.length() - 2; while( str.charAt( i ) != '/' ) { i--; } return str.substring( 0, i + 1 ); } return "smb://"; } /** * Returns the full uncanonicalized URL of this SMB resource. An * SmbFile constructed with the result of this method will * result in an SmbFile that is equal to the original. * * @return The uncanonicalized full URL of this SMB resource. */ public String getPath() { return url.toString(); } String getUncPath0() { if( unc == null ) { char[] in = url.getPath().toCharArray(); char[] out = new char[in.length]; int length = in.length, i, o, state, s; /* The canonicalization routine */ state = 0; o = 0; for( i = 0; i < length; i++ ) { switch( state ) { case 0: if( in[i] != '/' ) { return null; } out[o++] = in[i]; state = 1; break; case 1: if( in[i] == '/' ) { break; } else if( in[i] == '.' && (( i + 1 ) >= length || in[i + 1] == '/' )) { i++; break; } else if(( i + 1 ) < length && in[i] == '.' && in[i + 1] == '.' && (( i + 2 ) >= length || in[i + 2] == '/' )) { i += 2; if( o == 1 ) break; do { o--; } while( o > 1 && out[o - 1] != '/' ); break; } state = 2; case 2: if( in[i] == '/' ) { state = 1; } out[o++] = in[i]; break; } } canon = new String( out, 0, o ); if( o > 1 ) { o--; i = canon.indexOf( '/', 1 ); if( i < 0 ) { share = canon.substring( 1 ); unc = "\\"; } else if( i == o ) { share = canon.substring( 1, i ); unc = "\\"; } else { share = canon.substring( 1, i ); unc = canon.substring( i, out[o] == '/' ? o : o + 1 ); unc = unc.replace( '/', '\\' ); } } else { share = null; unc = "\\"; } } return unc; } /** * Retuns the Windows UNC style path with backslashs intead of forward slashes. * * @return The UNC path. */ public String getUncPath() { getUncPath0(); if( share == null ) { return "\\\\" + url.getHost(); } return "\\\\" + url.getHost() + canon.replace( '/', '\\' ); } /** * Returns the full URL of this SMB resource with '.' and '..' components * factored out. An SmbFile constructed with the result of * this method will result in an SmbFile that is equal to * the original. * * @return The canonicalized URL of this SMB resource. */ public String getCanonicalPath() { String str = url.getAuthority(); getUncPath0(); if( str.length() > 0 ) { return "smb://" + url.getAuthority() + canon; } return "smb://"; } /** * Retrieves the share associated with this SMB resource. In * the case of smb://, smb://workgroup/, * and smb://server/ URLs which do not specify a share, * null will be returned. * * @return The share component or null if there is no share */ public String getShare() { return share; } /** * Retrieve the hostname of the server for this SMB resource. If this * SmbFile references a workgroup, the name of the workgroup * is returned. If this SmbFile refers to the root of this * SMB network hierarchy, null is returned. * * @return The server or workgroup name or null if this * SmbFile refers to the root smb:// resource. */ public String getServer() { String str = url.getHost(); if( str.length() == 0 ) { return null; } return str; } /** * Returns type of of object this SmbFile represents. * @return TYPE_FILESYSTEM, TYPE_WORKGROUP, TYPE_SERVER, TYPE_SHARE, * TYPE_PRINTER, TYPE_NAMED_PIPE, or TYPE_COMM. */ public int getType() throws SmbException { if( type == 0 ) { if( getUncPath0().length() > 1 ) { type = TYPE_FILESYSTEM; } else if( share != null ) { // treeConnect good enough to test service type connect0(); if( share.equals( "IPC$" )) { type = TYPE_NAMED_PIPE; } else if( tree.service.equals( "LPT1:" )) { type = TYPE_PRINTER; } else if( tree.service.equals( "COMM" )) { type = TYPE_COMM; } else { type = TYPE_SHARE; } } else if( url.getAuthority().length() == 0 ) { type = TYPE_WORKGROUP; } else { UniAddress addr; try { addr = getAddress(); } catch( UnknownHostException uhe ) { throw new SmbException( url.toString(), uhe ); } if( addr.getAddress() instanceof NbtAddress ) { int code = ((NbtAddress)addr.getAddress()).getNameType(); if( code == 0x1d || code == 0x1b ) { type = TYPE_WORKGROUP; return type; } } type = TYPE_SERVER; } } return type; } boolean isWorkgroup0() throws UnknownHostException { if( type == TYPE_WORKGROUP || url.getHost().length() == 0 ) { type = TYPE_WORKGROUP; return true; } else { getUncPath0(); if( share == null ) { UniAddress addr = getAddress(); if( addr.getAddress() instanceof NbtAddress ) { int code = ((NbtAddress)addr.getAddress()).getNameType(); if( code == 0x1d || code == 0x1b ) { type = TYPE_WORKGROUP; return true; } } type = TYPE_SERVER; } } return false; } Info queryPath( String path, int infoLevel ) throws SmbException { connect0(); if( log.level > 2 ) log.println( "queryPath: " + path ); /* normally we'd check the negotiatedCapabilities for CAP_NT_SMBS * however I can't seem to get a good last modified time from * SMB_COM_QUERY_INFORMATION so if NT_SMBs are requested * by the server than in this case that's what it will get * regardless of what jcifs.smb.client.useNTSmbs is set * to(overrides negotiatedCapabilities). */ /* We really should do the referral before this in case * the redirected target has different capabilities. But * the way we have been doing that is to call exists() which * calls this method so another technique will be necessary * to support DFS referral _to_ Win95/98/ME. */ if( tree.session.transport.hasCapability( ServerMessageBlock.CAP_NT_SMBS )) { /* * Trans2 Query Path Information Request / Response */ Trans2QueryPathInformationResponse response = new Trans2QueryPathInformationResponse( infoLevel ); sendTransaction( new Trans2QueryPathInformation( path, infoLevel ), response ); return response.info; } else { /* * Query Information Request / Response */ SmbComQueryInformationResponse response = new SmbComQueryInformationResponse( tree.session.transport.server.serverTimeZone * 1000 * 60L ); send( new SmbComQueryInformation( path ), response ); return response; } } /** * Tests to see if the SMB resource exists. If the resource refers * only to a server, this method determines if the server exists on the * network and is advertising SMB services. If this resource refers to * a workgroup, this method determines if the workgroup name is valid on * the local SMB network. If this SmbFile refers to the root * smb:// resource true is always returned. If * this SmbFile is a traditional file or directory, it will * be queried for on the specified server as expected. * * @return true if the resource exists or is alive or * false otherwise */ public boolean exists() throws SmbException { if( attrExpiration > System.currentTimeMillis() ) { return isExists; } attributes = ATTR_READONLY | ATTR_DIRECTORY; createTime = 0L; lastModified = 0L; isExists = false; try { if( url.getHost().length() == 0 ) { } else if( share == null ) { if( getType() == TYPE_WORKGROUP ) { UniAddress.getByName( url.getHost(), true ); } else { UniAddress.getByName( url.getHost() ).getHostName(); } } else if( getUncPath0().length() == 1 || share.equalsIgnoreCase( "IPC$" )) { connect0(); // treeConnect is good enough } else { Info info = queryPath( getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO ); attributes = info.getAttributes(); createTime = info.getCreateTime(); lastModified = info.getLastWriteTime(); } /* If any of the above fail, isExists will not be set true */ isExists = true; } catch( UnknownHostException uhe ) { } catch( SmbException se ) { switch (se.getNtStatus()) { case NtStatus.NT_STATUS_NO_SUCH_FILE: case NtStatus.NT_STATUS_OBJECT_NAME_INVALID: case NtStatus.NT_STATUS_OBJECT_NAME_NOT_FOUND: case NtStatus.NT_STATUS_OBJECT_PATH_NOT_FOUND: break; default: throw se; } } attrExpiration = System.currentTimeMillis() + attrExpirationPeriod; return isExists; } /** * Tests to see if the file this SmbFile represents can be * read. Because any file, directory, or other resource can be read if it * exists, this method simply calls the exists method. * * @return true if the file is read-only */ public boolean canRead() throws SmbException { if( getType() == TYPE_NAMED_PIPE ) { // try opening the pipe for reading? return true; } return exists(); // try opening and catch sharing violation? } /** * Tests to see if the file this SmbFile represents * exists and is not marked read-only. By default, resources are * considered to be read-only and therefore for smb://, * smb://workgroup/, and smb://server/ resources * will be read-only. * * @return true if the resource exists is not marked * read-only */ public boolean canWrite() throws SmbException { if( getType() == TYPE_NAMED_PIPE ) { // try opening the pipe for writing? return true; } return exists() && ( attributes & ATTR_READONLY ) == 0; } /** * Tests to see if the file this SmbFile represents is a directory. * * @return true if this SmbFile is a directory */ public boolean isDirectory() throws SmbException { if( getUncPath0().length() == 1 ) { return true; } if (!exists()) return false; return ( attributes & ATTR_DIRECTORY ) == ATTR_DIRECTORY; } /** * Tests to see if the file this SmbFile represents is not a directory. * * @return true if this SmbFile is not a directory */ public boolean isFile() throws SmbException { if( getUncPath0().length() == 1 ) { return false; } exists(); return ( attributes & ATTR_DIRECTORY ) == 0; } /** * Tests to see if the file this SmbFile represents is marked as * hidden. This method will also return true for shares with names that * end with '$' such as IPC$ or C$. * * @return true if the SmbFile is marked as being hidden */ public boolean isHidden() throws SmbException { if( share == null ) { return false; } else if( getUncPath0().length() == 1 ) { if( share.endsWith( "$" )) { return true; } return false; } exists(); return ( attributes & ATTR_HIDDEN ) == ATTR_HIDDEN; } /** * If the path of this SmbFile falls within a DFS volume, * this method will return the referral path to which it maps. Otherwise * null is returned. */ public String getDfsPath() throws SmbException { connect0(); if( tree.inDfs ) { exists(); } if( dfsReferral == null ) { return null; } return "smb:/" + (new String( dfsReferral.node + unc )).replace( '\\', '/' ); } /** * Retrieve the time this SmbFile was created. The value * returned is suitable for constructing a {@link java.util.Date} object * (i.e. seconds since Epoch 1970). Times should be the same as those * reported using the properties dialog of the Windows Explorer program. * * For Win95/98/Me this is actually the last write time. It is currently * not possible to retrieve the create time from files on these systems. * * @return The number of milliseconds since the 00:00:00 GMT, January 1, * 1970 as a long value */ public long createTime() throws SmbException { if( getUncPath0().length() > 1 ) { exists(); return createTime; } return 0L; } /** * Retrieve the last time the file represented by this * SmbFile was modified. The value returned is suitable for * constructing a {@link java.util.Date} object (i.e. seconds since Epoch * 1970). Times should be the same as those reported using the properties * dialog of the Windows Explorer program. * * @return The number of milliseconds since the 00:00:00 GMT, January 1, * 1970 as a long value */ public long lastModified() throws SmbException { if( getUncPath0().length() > 1 ) { exists(); return lastModified; } return 0L; } /** * List the contents of this SMB resource. The list returned by this * method will be; * *

* * @return A String[] array of files and directories, * workgroups, servers, or shares depending on the context of the * resource URL */ public String[] list() throws SmbException { return list( "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null ); } /** * List the contents of this SMB resource. The list returned will be * identical to the list returned by the parameterless list() * method minus filenames filtered by the specified filter. * * @param filter a filename filter to exclude filenames from the results * @throws SmbException # @return An array of filenames */ public String[] list( SmbFilenameFilter filter ) throws SmbException { return list( "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null ); } /** * List the contents of this SMB resource as an array of * SmbFile objects. This method is much more efficient than * the regular list method when querying attributes of each * file in the result set. *

* The list of SmbFiles returned by this method will be; * *

* * @return An array of SmbFile objects representing file * and directories, workgroups, servers, or shares depending on the context * of the resource URL */ public SmbFile[] listFiles() throws SmbException { return listFiles( "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null ); } /** * The CIFS protocol provides for DOS "wildcards" to be used as * a performance enhancement. The client does not have to filter * the names and the server does not have to return all directory * entries. *

* The wildcard expression may consist of two special meta * characters in addition to the normal filename characters. The '*' * character matches any number of characters in part of a name. If * the expression begins with one or more '?'s then exactly that * many characters will be matched whereas if it ends with '?'s * it will match that many characters or less. *

* Wildcard expressions will not filter workgroup names or server names. * *

 * winnt> ls c?o*
 * clock.avi                  -rw--      82944 Mon Oct 14 1996 1:38 AM
 * Cookies                    drw--          0 Fri Nov 13 1998 9:42 PM
 * 2 items in 5ms
 * 
* * @param wildcard a wildcard expression * @throws SmbException * @return An array of SmbFile objects representing file * and directories, workgroups, servers, or shares depending on the context * of the resource URL */ public SmbFile[] listFiles( String wildcard ) throws SmbException { return listFiles( wildcard, ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null ); } /** * List the contents of this SMB resource. The list returned will be * identical to the list returned by the parameterless listFiles() * method minus files filtered by the specified filename filter. * * @param filter a filter to exclude files from the results * @return An array of SmbFile objects * @throws SmbException */ public SmbFile[] listFiles( SmbFilenameFilter filter ) throws SmbException { return listFiles( "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, filter, null ); } /** * List the contents of this SMB resource. The list returned will be * identical to the list returned by the parameterless listFiles() * method minus filenames filtered by the specified filter. * * @param filter a file filter to exclude files from the results * @return An array of SmbFile objects */ public SmbFile[] listFiles( SmbFileFilter filter ) throws SmbException { return listFiles( "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, filter ); } String[] list( String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException { ArrayList list = new ArrayList(); try { if( url.getHost().length() == 0 || share == null ) { doNetEnum( list, false, wildcard, searchAttributes, fnf, ff ); } else { doFindFirstNext( list, false, wildcard, searchAttributes, fnf, ff ); } } catch( UnknownHostException uhe ) { throw new SmbException( url.toString(), uhe ); } catch( MalformedURLException mue ) { throw new SmbException( url.toString(), mue ); } return (String[])list.toArray(new String[list.size()]); } SmbFile[] listFiles( String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException { ArrayList list = new ArrayList(); if( ff != null && ff instanceof DosFileFilter ) { DosFileFilter dff = (DosFileFilter)ff; if( dff.wildcard != null ) { wildcard = dff.wildcard; } searchAttributes = dff.attributes; } try { if( url.getHost().length() == 0 || share == null ) { doNetEnum( list, true, wildcard, searchAttributes, fnf, ff ); } else { doFindFirstNext( list, true, wildcard, searchAttributes, fnf, ff ); } } catch( UnknownHostException uhe ) { throw new SmbException( url.toString(), uhe ); } catch( MalformedURLException mue ) { throw new SmbException( url.toString(), mue ); } return (SmbFile[])list.toArray(new SmbFile[list.size()]); } void doNetEnum( ArrayList list, boolean files, String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException, UnknownHostException, MalformedURLException { SmbComTransaction req; SmbComTransactionResponse resp; int listType = url.getAuthority().length() == 0 ? 0 : getType(); String p = url.getPath(); if( p.lastIndexOf( '/' ) != ( p.length() - 1 )) { throw new SmbException( url.toString() + " directory must end with '/'" ); } switch( listType ) { case 0: connect0(); req = new NetServerEnum2( tree.session.transport.server.oemDomainName, NetServerEnum2.SV_TYPE_DOMAIN_ENUM ); resp = new NetServerEnum2Response(); break; case TYPE_WORKGROUP: req = new NetServerEnum2( url.getHost(), NetServerEnum2.SV_TYPE_ALL ); resp = new NetServerEnum2Response(); break; case TYPE_SERVER: req = new NetShareEnum(); resp = new NetShareEnumResponse(); break; default: throw new SmbException( "The requested list operations is invalid: " + url.toString() ); } boolean more; do { int n; sendTransaction( req, resp ); more = resp.status == SmbException.ERROR_MORE_DATA; if( resp.status != SmbException.ERROR_SUCCESS && resp.status != SmbException.ERROR_MORE_DATA ) { throw new SmbException( resp.status, true ); } n = more ? resp.numEntries - 1 : resp.numEntries; for( int i = 0; i < n; i++ ) { FileEntry e = resp.results[i]; String name = e.getName(); if( fnf != null && fnf.accept( this, name ) == false ) { continue; } if( name.length() > 0 ) { SmbFile f = new SmbFile( this, name, listType == 0 ? TYPE_WORKGROUP : (listType << 1), ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L, 0L ); if( ff != null && ff.accept( f ) == false ) { continue; } if( files ) { list.add( f ); } else { list.add( name ); } } } if( listType != 0 && listType != TYPE_WORKGROUP ) { break; } req.subCommand = (byte)SmbComTransaction.NET_SERVER_ENUM3; req.reset( 0, ((NetServerEnum2Response)resp).lastName ); resp.reset(); } while( more ); } void doFindFirstNext( ArrayList list, boolean files, String wildcard, int searchAttributes, SmbFilenameFilter fnf, SmbFileFilter ff ) throws SmbException, UnknownHostException, MalformedURLException { SmbComTransaction req; Trans2FindFirst2Response resp; int sid; String path = getUncPath0(); String p = url.getPath(); if( p.lastIndexOf( '/' ) != ( p.length() - 1 )) { throw new SmbException( url.toString() + " directory must end with '/'" ); } req = new Trans2FindFirst2( path, wildcard, searchAttributes ); resp = new Trans2FindFirst2Response(); if( log.level > 2 ) log.println( "doFindFirstNext: " + req.path ); sendTransaction( req, resp ); sid = resp.sid; req = new Trans2FindNext2( sid, resp.resumeKey, resp.lastName ); /* The only difference between first2 and next2 responses is subCommand * so let's recycle the response object. */ resp.subCommand = SmbComTransaction.TRANS2_FIND_NEXT2; for( ;; ) { for( int i = 0; i < resp.numEntries; i++ ) { FileEntry e = resp.results[i]; String name = e.getName(); if( name.length() < 3 ) { int h = name.hashCode(); if( h == HASH_DOT || h == HASH_DOT_DOT ) { continue; } } if( fnf != null && fnf.accept( this, name ) == false ) { continue; } if( name.length() > 0 ) { SmbFile f = new SmbFile( this, name, TYPE_FILESYSTEM, e.getAttributes(), e.createTime(), e.lastModified(), e.length() ); if( ff != null && ff.accept( f ) == false ) { continue; } if( files ) { list.add( f ); } else { list.add( name ); } } } if( resp.isEndOfSearch || resp.numEntries == 0 ) { break; } req.reset( resp.resumeKey, resp.lastName ); resp.reset(); sendTransaction( req, resp ); } send( new SmbComFindClose2( sid ), blank_resp() ); } /** * Changes the name of the file this SmbFile represents to the name * designated by the SmbFile argument. *

* Remember: SmbFiles are immutible and therefore * the path associated with this SmbFile object will not * change). To access the renamed file it is necessary to construct a * new SmbFile. * * @param dest An SmbFile that represents the new pathname * @return true if the file or directory was successfully renamed * @throws NullPointerException * If the dest argument is null */ public void renameTo( SmbFile dest ) throws SmbException { if( getUncPath0().length() == 1 || dest.getUncPath0().length() == 1 ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } connect0(); dest.connect0(); if( tree.inDfs ) { /* This ensures that each path is * resolved independantly to deal with the case where files * have the same base path but ultimately turn out to be * on different servers because of DFS. It also eliminates * manipulating the SMB path which is problematic because * there are really two that would need to be prefixed * with host and share as DFS requires. */ exists(); dest.exists(); } if( tree != dest.tree ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } if( log.level > 2 ) log.println( "renameTo: " + unc + " -> " + dest.unc ); attrExpiration = sizeExpiration = 0; dest.attrExpiration = 0; /* * Rename Request / Response */ send( new SmbComRename( unc, dest.unc ), blank_resp() ); } class WriterThread extends Thread { byte[] b; int n, off; boolean ready; SmbFile dest; SmbException e = null; boolean useNTSmbs; SmbComWriteAndX reqx; SmbComWrite req; ServerMessageBlock resp; WriterThread() throws SmbException { super( "JCIFS-WriterThread" ); useNTSmbs = tree.session.transport.hasCapability( ServerMessageBlock.CAP_NT_SMBS ); if( useNTSmbs ) { reqx = new SmbComWriteAndX(); resp = new SmbComWriteAndXResponse(); } else { req = new SmbComWrite(); resp = new SmbComWriteResponse(); } ready = false; } synchronized void write( byte[] b, int n, SmbFile dest, int off ) { this.b = b; this.n = n; this.dest = dest; this.off = off; ready = false; notify(); } public void run() { synchronized( this ) { try { for( ;; ) { notify(); ready = true; while( ready ) { wait(); } if( n == -1 ) { return; } if( useNTSmbs ) { reqx.setParam( dest.fid, off, n, b, 0, n ); dest.send( reqx, resp ); } else { req.setParam( dest.fid, off, n, b, 0, n ); dest.send( req, resp ); } } } catch( SmbException e ) { this.e = e; } catch( Exception x ) { this.e = new SmbException( "WriterThread", x ); } notify(); } } } void copyTo0( SmbFile dest, byte[][] b, int bsize, WriterThread w, SmbComReadAndX req, SmbComReadAndXResponse resp ) throws SmbException { int i; if( attrExpiration < System.currentTimeMillis() ) { attributes = ATTR_READONLY | ATTR_DIRECTORY; createTime = 0L; lastModified = 0L; isExists = false; Info info = queryPath( getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO ); attributes = info.getAttributes(); createTime = info.getCreateTime(); lastModified = info.getLastWriteTime(); /* If any of the above fails, isExists will not be set true */ isExists = true; attrExpiration = System.currentTimeMillis() + attrExpirationPeriod; } if( isDirectory() ) { SmbFile[] files; SmbFile ndest; try { dest.mkdir(); dest.setPathInformation( attributes, createTime, lastModified ); } catch( SmbException se ) { if( se.getNtStatus() != NtStatus.NT_STATUS_ACCESS_DENIED && se.getNtStatus() != NtStatus.NT_STATUS_OBJECT_NAME_COLLISION ) { throw se; } } files = listFiles( "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null ); try { for( i = 0; i < files.length; i++ ) { ndest = new SmbFile( dest, files[i].getName(), files[i].type, files[i].attributes, files[i].createTime, files[i].lastModified, files[i].size ); files[i].copyTo0( ndest, b, bsize, w, req, resp ); } } catch( UnknownHostException uhe ) { throw new SmbException( url.toString(), uhe ); } catch( MalformedURLException mue ) { throw new SmbException( url.toString(), mue ); } } else { int off; open( SmbFile.O_RDONLY, ATTR_NORMAL, 0 ); try { dest.open( SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC | SmbComNTCreateAndX.FILE_WRITE_ATTRIBUTES << 16, attributes, 0 ); } catch( SmbAuthException sae ) { if(( dest.attributes & ATTR_READONLY ) != 0 ) { /* Remove READONLY and try again */ dest.setPathInformation( dest.attributes & ~ATTR_READONLY, 0L, 0L ); dest.open( SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC | SmbComNTCreateAndX.FILE_WRITE_ATTRIBUTES << 16, attributes, 0 ); } else { throw sae; } } i = off = 0; for( ;; ) { req.setParam( fid, off, bsize ); resp.setParam( b[i], 0 ); send( req, resp ); synchronized( w ) { while( !w.ready ) { try { w.wait(); } catch( InterruptedException ie ) { throw new SmbException( dest.url.toString(), ie ); } } if( w.e != null ) { throw w.e; } if( resp.dataLength <= 0 ) { break; } w.write( b[i], resp.dataLength, dest, off ); } i = i == 1 ? 0 : 1; off += resp.dataLength; } dest.sendTransaction( new Trans2SetFileInformation( dest.fid, attributes, createTime, lastModified ), new Trans2SetFileInformationResponse() ); dest.close( 0L ); close(); } } /** * This method will copy the file or directory represented by this * SmbFile and it's sub-contents to the location specified by the * dest parameter. This file and the destination file do not * need to be on the same host. This operation does not copy extended * file attibutes such as ACLs but it does copy regular attributes as * well as create and last write times. This method is almost twice as * efficient as manually copying as it employs an additional write * thread to read and write data concurrently. *

* It is not possible (nor meaningful) to copy entire workgroups or * servers. * * @param dest the destination file or directory * @throw SmbException */ public void copyTo( SmbFile dest ) throws SmbException { SmbComReadAndX req; SmbComReadAndXResponse resp; WriterThread w; int bsize; byte[][] b; /* Should be able to copy an entire share actually */ if( share == null || dest.share == null) { throw new SmbException( "Invalid operation for workgroups or servers" ); } req = new SmbComReadAndX(); resp = new SmbComReadAndXResponse(); connect0(); dest.connect0(); if( tree.inDfs ) { /* At this point the maxBufferSize values are from the server * exporting the volumes, not the one that we will actually * end up performing IO with. If the server hosting the * actual files has a smaller maxBufSize this could be * incorrect. To handle this properly it is necessary * to redirect the tree to the target server first before * establishing buffer size. These exists() calls facilitate * that. */ exists(); dest.exists(); } w = new WriterThread(); w.setDaemon( true ); w.start(); bsize = Math.min( tree.session.transport.rcv_buf_size - 70, tree.session.transport.snd_buf_size - 70 ); b = new byte[2][bsize]; copyTo0( dest, b, bsize, w, req, resp ); w.write( null, -1, null, 0 ); } /** * This method will delete the file or directory specified by this * SmbFile. If the target is a directory, the contents of * the directory will be deleted as well. If a file within the directory or * it's sub-directories is marked read-only, the read-only status will * be removed and the file will be deleted. * * @throws SmbException */ public void delete() throws SmbException { if( tree == null || tree.inDfs ) { exists(); /* This is necessary to ensure we * pass a path adjusted for DFS */ } getUncPath0(); delete( unc ); } void delete( String fileName ) throws SmbException { if( getUncPath0().length() == 1 ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } if( System.currentTimeMillis() > attrExpiration ) { attributes = ATTR_READONLY | ATTR_DIRECTORY; createTime = 0L; lastModified = 0L; isExists = false; Info info = queryPath( getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO ); attributes = info.getAttributes(); createTime = info.getCreateTime(); lastModified = info.getLastWriteTime(); attrExpiration = System.currentTimeMillis() + attrExpirationPeriod; isExists = true; } if(( attributes & ATTR_READONLY ) != 0 ) { setReadWrite(); } /* * Delete or Delete Directory Request / Response */ if( log.level > 2 ) log.println( "delete: " + fileName ); if(( attributes & ATTR_DIRECTORY ) != 0 ) { /* Recursively delete directory contents */ SmbFile[] l = listFiles( "*", ATTR_DIRECTORY | ATTR_HIDDEN | ATTR_SYSTEM, null, null ); for( int i = 0; i < l.length; i++ ) { l[i].delete(); } send( new SmbComDeleteDirectory( fileName ), blank_resp() ); } else { send( new SmbComDelete( fileName ), blank_resp() ); } attrExpiration = sizeExpiration = 0; } /** * Returns the length of this SmbFile in bytes. If this object * is a TYPE_SHARE the total capacity of the disk shared in * bytes is returned. If this object is a directory or a type other than * TYPE_SHARE, 0L is returned. * * @return The length of the file in bytes or 0 if this * SmbFile is not a file. * @throw SmbException */ public long length() throws SmbException { if( sizeExpiration > System.currentTimeMillis() ) { return size; } if( getType() == TYPE_SHARE ) { Trans2QueryFSInformationResponse response; int level = Trans2QueryFSInformationResponse.SMB_INFO_ALLOCATION; response = new Trans2QueryFSInformationResponse( level ); sendTransaction( new Trans2QueryFSInformation( level ), response ); size = response.info.getCapacity(); } else if( getUncPath0().length() > 1 && type != TYPE_NAMED_PIPE ) { Info info = queryPath( getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_STANDARD_INFO ); size = info.getSize(); } else { size = 0L; } sizeExpiration = System.currentTimeMillis() + attrExpirationPeriod; return size; } /** * This method returns the free disk space in bytes of the drive this share * represents or the drive on which the directory or file resides. Objects * other than TYPE_SHARE or TYPE_FILESYSTEM will result * in 0L being returned. * * @return the free disk space in bytes of the drive on which this file or * directory resides */ public long getDiskFreeSpace() throws SmbException { if( getType() == TYPE_SHARE || type == TYPE_FILESYSTEM ) { Trans2QueryFSInformationResponse response; int level = Trans2QueryFSInformationResponse.SMB_INFO_ALLOCATION; response = new Trans2QueryFSInformationResponse( level ); sendTransaction( new Trans2QueryFSInformation( level ), response ); if( type == TYPE_SHARE ) { size = response.info.getCapacity(); sizeExpiration = System.currentTimeMillis() + attrExpirationPeriod; } return response.info.getFree(); } return 0L; } /** * Creates a directory with the path specified by this * SmbFile. For this method to be successful, the target * must not already exist. This method will fail when * used with smb://, smb://workgroup/, * smb://server/, or smb://server/share/ URLs * because workgroups, servers, and shares cannot be dynamically created * (although in the future it may be possible to create shares). * * @throws SmbException */ public void mkdir() throws SmbException { String path = getUncPath0(); if( path.length() == 1 ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } /* * Create Directory Request / Response */ if( log.level > 2 ) log.println( "mkdir: " + path ); send( new SmbComCreateDirectory( path ), blank_resp() ); attrExpiration = sizeExpiration = 0; } /** * Creates a directory with the path specified by this SmbFile * and any parent directories that do not exist. This method will fail * when used with smb://, smb://workgroup/, * smb://server/, or smb://server/share/ URLs * because workgroups, servers, and shares cannot be dynamically created * (although in the future it may be possible to create shares). * * @throws SmbException */ public void mkdirs() throws SmbException { SmbFile parent; try { parent = new SmbFile( getParent(), auth ); } catch( IOException ioe ) { return; } if( parent.exists() == false ) { parent.mkdirs(); } mkdir(); } /** * Create a new file but fail if it already exists. The check for * existance of the file and it's creation are an atomic operation with * respect to other filesystem activities. */ public void createNewFile() throws SmbException { if( getUncPath0().length() == 1 ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } close( open0( O_RDWR | O_CREAT | O_EXCL, ATTR_NORMAL, 0 ), 0L ); } void setPathInformation( int attrs, long ctime, long mtime ) throws SmbException { int f, options = 0; if(( attrs & ATTR_DIRECTORY ) != 0 ) { options = 1; } f = open0( O_RDONLY | SmbComNTCreateAndX.FILE_WRITE_ATTRIBUTES << 16, attrs, options ); sendTransaction( new Trans2SetFileInformation( f, attrs, ctime, mtime ), new Trans2SetFileInformationResponse() ); close( f, 0L ); attrExpiration = 0; } /** * Set the create time of the file. The time is specified as milliseconds * from Jan 1, 1970 which is the same as that which is returned by the * createTime() method. *

* This method does not apply to workgroups, servers, or shares. * * @param time the create time as milliseconds since Jan 1, 1970 */ public void setCreateTime( long time ) throws SmbException { if( getUncPath0().length() == 1 ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } setPathInformation( 0, time, 0L ); } /** * Set the last modified time of the file. The time is specified as milliseconds * from Jan 1, 1970 which is the same as that which is returned by the * lastModified(), getLastModified(), and getDate() methods. *

* This method does not apply to workgroups, servers, or shares. * * @param time the last modified time as milliseconds since Jan 1, 1970 */ public void setLastModified( long time ) throws SmbException { if( getUncPath0().length() == 1 ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } setPathInformation( 0, 0L, time ); } /** * Return the attributes of this file. Attributes are represented as a * bitset that must be masked with ATTR_* constants to determine * if they are set or unset. The value returned is suitable for use with * the setAttributes() method. * * @return the ATTR_* attributes associated with this file * @throw SmbException */ public int getAttributes() throws SmbException { if( getUncPath0().length() == 1 ) { return 0; } exists(); return attributes & ATTR_GET_MASK; } /** * Set the attributes of this file. Attributes are composed into a * bitset by bitwise ORing the ATTR_* constants. Setting the * value returned by getAttributes will result in both files * having the same attributes. * @throw SmbException */ public void setAttributes( int attrs ) throws SmbException { if( getUncPath0().length() == 1 ) { throw new SmbException( "Invalid operation for workgroups, servers, or shares" ); } setPathInformation( attrs & ATTR_SET_MASK, 0L, 0L ); } /** * Make this file read-only. This is shorthand for setAttributes( * getAttributes() | ATTR_READ_ONLY ). * * @throw SmbException */ public void setReadOnly() throws SmbException { setAttributes( getAttributes() | ATTR_READONLY ); } /** * Turn off the read-only attribute of this file. This is shorthand for * setAttributes( getAttributes() & ~ATTR_READONLY ). * * @throw SmbException */ public void setReadWrite() throws SmbException { setAttributes( getAttributes() & ~ATTR_READONLY ); } /** * Returns a {@link java.net.URL} for this SmbFile. The * URL may be used as any other URL might to * access an SMB resource. Currently only retrieving data and information * is supported (i.e. no doOutput). * * @depricated Use getURL() instead * @return A new {@link java.net.URL} for this SmbFile * @throw MalformedURLException */ public URL toURL() throws MalformedURLException { return url; } /** * Computes a hashCode for this file based on the URL string and IP * address if the server. The hashing function uses the hashcode of the * server address, the canonical representation of the URL, and does not * compare authentication information. In essance, two * SmbFile objects that refer to * the same file should generate the same hashcode provided it is possible * to make such a determination. * * @return A hashcode for this abstract file * @throw SmbException */ public int hashCode() { int hash; try { hash = getAddress().hashCode(); } catch( UnknownHostException uhe ) { hash = getServer().toUpperCase().hashCode(); } getUncPath0(); return hash + canon.toUpperCase().hashCode(); } /** * Tests to see if two SmbFile objects are equal. Two * SmbFile objects are equal when they reference the same SMB * resource. More specifically, two SmbFile objects are * equals if their server IP addresses are equal and the canonicalized * representation of their URLs, minus authentication parameters, are * case insensitivly and lexographically equal. *

* For example, assuming the server angus resolves to the * 192.168.1.15 IP address, the below URLs would result in * SmbFiles that are equal. * *

 * smb://192.168.1.15/share/DIR/foo.txt
 * smb://angus/share/data/../dir/foo.txt
 * 
* * @param obj Another SmbFile object to compare for equality * @return true if the two objects refer to the same SMB resource * and false otherwise * @throw SmbException */ public boolean equals( Object obj ) { return obj instanceof SmbFile && obj.hashCode() == hashCode(); } /** * Returns the string representation of this SmbFile object. This will * be the same as the URL used to construct this SmbFile. * This method will return the same value * as getPath. * * @return The original URL representation of this SMB resource * @throw SmbException */ public String toString() { return url.toString(); } /* URLConnection implementation */ /** * This URLConnection method just returns the result of length(). * * @return the length of this file or 0 if it refers to a directory */ public int getContentLength() { try { return (int)(length() & 0xFFFFFFFFL); } catch( SmbException se ) { } return 0; } /** * This URLConnection method just returns the result of lastModified. * * @return the last modified data as milliseconds since Jan 1, 1970 */ public long getDate() { try { return lastModified(); } catch( SmbException se ) { } return 0L; } /** * This URLConnection method just returns the result of lastModified. * * @return the last modified data as milliseconds since Jan 1, 1970 */ public long getLastModified() { try { return lastModified(); } catch( SmbException se ) { } return 0L; } /** * This URLConnection method just returns a new SmbFileInputStream created with this file. * * @throw IOException thrown by SmbFileInputStream constructor */ public InputStream getInputStream() throws IOException { return new SmbFileInputStream( this ); } }