/* 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 jcifs.util.Config; 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* to specify the target file or * directory. SmbFile URLs have the following syntax: * *

 *     smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]]
 * 
* * This example: * *
 *     smb://storage15/public/foo.txt
 * 
* * would referece 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.

* Please note that beginning with jcifs-0.7.0b4 all SMB URLs that represent workgroups, servers, shares, or directories require a trailing slash '/'. This will break older code and you may or may not get an exception. For example:

smb://server/share/path/to/dir/  <-- GOOD

smb://server/share/path/to/dir   <-- BAD
To assist you with this, the getName method will now include a slash '/' after the name if the URL refers to a directory (e.g. 'dir/').

When using the java.net.URL class with 'smb://' URLs it is necessary to first call the static jcifs.Config.registerSmbURLHandler(); method (prior to jcifs-0.7.0b12 it was Class.forName( "jcifs.Config" ); but this is no longer necessary).

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.

When used in conjunction with the list * method, this functionality can be usefull for network diagnostics * tools or "Network Neighborhood" like functionality. 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: * *

* 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 '/'. *
* *

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 { // 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; // 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 static final int ATTR_READONLY = 0x01; static final int ATTR_HIDDEN = 0x02; static final int ATTR_SYSTEM = 0x04; static final int ATTR_VOLUME = 0x08; static final int ATTR_DIRECTORY = 0x10; static final int ATTR_ARCHIVE = 0x20; static final int DEFAULT_ATTR_EXPIRATION_PERIOD = 5000; 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; SmbTree tree = null; // Initially null; may be !tree.treeConnected String canon; // Initially null; set by getUncPath; dir must end with '/' String unc; // Initially null; set by getUncPath; never ends with '/' String share; // Can be null int fid; // Initially 0; set by open() int type; long lastModified; int attributes; long attrExpiration; long size; long sizeExpiration; NtlmPasswordAuthentication auth; // Cannot be null boolean opened; boolean isExists; SmbComBlankResponse blank_resp = new SmbComBlankResponse(); boolean open_exclusive=false; /** * 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 parent A base SmbFile * @param child 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 parent A URL string * @param child A path string relative to the parent paremeter * @throws MalformedURLException * If the parent and child 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 )); } public SmbFile( String context, String name,boolean open_exclusive ) throws MalformedURLException { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER )); this.open_exclusive = open_exclusive; } public SmbFile( String url, NtlmPasswordAuthentication auth ) throws MalformedURLException { this( new URL( null, url, Handler.SMB_HANDLER ), auth ); } public SmbFile( String url, NtlmPasswordAuthentication auth ,boolean open_exclusive ) throws MalformedURLException { this( new URL( null, url, Handler.SMB_HANDLER ), auth ); this.open_exclusive = open_exclusive; } 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 ); } public SmbFile( String context, String name, NtlmPasswordAuthentication auth,boolean open_exclusive ) throws MalformedURLException { this( new URL( new URL( null, context, Handler.SMB_HANDLER ), name, Handler.SMB_HANDLER ), auth ); this.open_exclusive = open_exclusive; } 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. The second parameter is a relative path from * the parent. See the description above for examples of * using the second chile parameter. * * @param parent A URL string * @open_exclusive open the file in exclusive mode * @throws MalformedURLException * If the parent and open_exclusive parameters * do not follow the prescribed syntax */ public SmbFile( URL url,boolean open_exclusive ) { this( url, new NtlmPasswordAuthentication( url.getUserInfo() )); this.open_exclusive = open_exclusive; } public SmbFile( URL url, NtlmPasswordAuthentication auth ) { super( url ); this.auth = auth; getUncPath0(); } public SmbFile( URL url, NtlmPasswordAuthentication auth,boolean open_exclusive ) { this (url,auth); this.open_exclusive = open_exclusive; } SmbFile( SmbFile context, String name, int type, int attributes, long lastModified, long size ) throws MalformedURLException, UnknownHostException { this( context, name + (( attributes & ATTR_DIRECTORY ) > 0 ? "/" : "" )); 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; } this.type = type; this.attributes = attributes; this.lastModified = lastModified; this.size = size; attrExpiration = sizeExpiration = System.currentTimeMillis() + attrExpirationPeriod; } void sendTransaction( SmbComTransaction request, SmbComTransactionResponse response ) throws SmbException { connect0(); tree.sendTransaction( request, response ); } void send( ServerMessageBlock request, ServerMessageBlock response ) throws SmbException { connect0(); tree.send( request, response ); } UniAddress getAddress() throws UnknownHostException { String host = url.getHost(); String path = url.getPath(); if( host.length() == 0 ) { return UniAddress.getByName( NbtAddress.getByName( NbtAddress.MASTER_BROWSER_NAME, 0x01, null).getHostAddress() ); } 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( SmbException.ERRCLI, SmbException.ERRunknownHost, "Unknown host: " + uhe.getMessage() ); } catch( SmbException se ) { throw se; } catch( IOException ioe ) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRioe, ioe.getMessage() ); } } 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 trans = SmbTransport.getSmbTransport( addr, url.getPort() ); 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; trans = SmbTransport.getSmbTransport( addr, url.getPort() ); ssn = trans.getSmbSession( auth ); tree = ssn.getSmbTree( share, null ); tree.treeConnect( null, null ); } else { throw sae; } } } boolean isConnected() { return (connected = tree != null && tree.treeConnected); } void open( int flags ) throws SmbException { if( isOpen() ) { return; } connect0(); Log.println( Log.WARNINGS, "smb open warning: ", unc ); /* * Open AndX Request / Response */ if( tree.session.transport.hasCapability( ServerMessageBlock.CAP_NT_SMBS )) { SmbComNTCreateAndXResponse response = new SmbComNTCreateAndXResponse(); send( new SmbComNTCreateAndX( unc, flags, null ,open_exclusive), response ); fid = response.fid; } else { SmbComOpenAndXResponse response = new SmbComOpenAndXResponse(); send( new SmbComOpenAndX( unc, flags, null ), response ); fid = response.fid; } opened = true; } boolean isOpen() { return opened && isConnected(); } void close() throws SmbException { close( 0L ); } void close( long lastWriteTime ) throws SmbException { if( isOpen() == false ) { return; } opened = false; Log.println( Log.WARNINGS, "smb close warning", " fid=" + fid ); /* * Close Request / Response */ send( new SmbComClose( fid, lastWriteTime ), blank_resp ); } /** * 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 introduced * in jcifs-0.7.0b4. * * @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() { StringBuffer sb = new StringBuffer( "smb://" ); String str = url.getAuthority(); if( str.length() > 0 ) { sb.append( str ); sb.append( '/' ); } getUncPath0(); if( canon.length() > 1 ) { int i = canon.length() - 2; while( canon.charAt( i ) != '/' ) { i--; } if( i > 1 ) { sb.append( canon.substring( 1, i )); sb.append( '/' ); } } return sb.toString(); } /** * 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 i, o, state, s; state = 0; o = 0; for( i = 0; i < in.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 ) >= in.length || in[i + 1] == '/' )) { i++; break; } else if(( i + 1 ) < in.length && in[i] == '.' && in[i + 1] == '.' && (( i + 2 ) >= in.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() { String path = getUncPath0(); if( share == null ) { return "\\\\" + url.getHost(); } return "\\\\" + url.getHost() + "\\" + share + path; } /** * 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( SmbException.ERRCLI | SmbException.ERRunknownHost, "Unknown host: " + uhe.getMessage() ); } 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 { SmbTransport trans; connect0(); Log.println( Log.WARNINGS, "smb query path warning", " querying path=" + 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). */ 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; lastModified = 0L; isExists = false; try { if( url.getHost().length() == 0 ) { } else if( share == null ) { if( true || type == 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(); lastModified = info.getLastWriteTime(); } /* If any of the above fail, isExists will not be set true */ isExists = true; } catch( UnknownHostException uhe ) { } catch( SmbException se ) { if( se.errorClass == SmbException.ERRDOS && ( se.errorCode == SmbException.ERRbadfile || se.errorCode == SmbException.ERRbadnetname || se.errorCode == SmbException.ERRbadpath )) { } else { 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; } exists(); 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. * * @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; } /** * 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 and is adjusted for * the servers timezone differential. 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 { connect0(); if( url.getHost().length() == 0 ) { NetServerEnum2Response response = new NetServerEnum2Response(); sendTransaction( new NetServerEnum2( tree.session.transport.server.oemDomainName, NetServerEnum2.SV_TYPE_DOMAIN_ENUM ), response ); if( response.status != SmbException.NERR_Success && response.status != SmbException.ERROR_MORE_DATA ) { throw new SmbException( SmbException.ERRRAP, response.status, response.toString() ); } String[] ret = new String[response.entriesReturned]; for( int i = 0; i < response.entriesReturned; i++ ) { ret[i] = response.results[i].name; } return ret; } else if( share == null ) { if( getType() == TYPE_WORKGROUP ) { NetServerEnum2Response response = new NetServerEnum2Response(); sendTransaction( new NetServerEnum2( url.getHost(), NetServerEnum2.SV_TYPE_ALL ), response ); if( response.status != SmbException.NERR_Success && response.status != SmbException.ERROR_MORE_DATA ) { throw new SmbException( SmbException.ERRRAP, response.status ); } String[] ret = new String[response.entriesReturned]; for( int i = 0; i < response.entriesReturned; i++ ) { ret[i] = response.results[i].name; } return ret; } else { NetShareEnumResponse response = new NetShareEnumResponse(); sendTransaction( new NetShareEnum(), response ); if( response.status != SmbException.NERR_Success && response.status != SmbException.ERROR_MORE_DATA ) { throw new SmbException( SmbException.ERRRAP, response.status ); } String[] ret = new String[response.entriesReturned]; for( int i = 0; i < response.entriesReturned; i++ ) { ret[i] = response.results[i].netName; } return ret; } } else { return list( unc ); } } String[] list( String dirPath ) throws SmbException { int sid, count, i, j; String[] results; String filename; Log.println( Log.WARNINGS, "smb find warning", " find with path=" + dirPath ); Trans2FindFirst2Response response = new Trans2FindFirst2Response(); sendTransaction( new Trans2FindFirst2( dirPath + "\\*" ), response ); sid = response.sid; count = response.searchCount; j = 0; results = new String[Math.max( 16, count )]; int h1 = new String( "." ).hashCode(); int h2 = new String( ".." ).hashCode(); i = 0; while( j < count ) { filename = response.results[i++].filename; if( filename.length() < 3 ) { int h = filename.hashCode(); if( h == h1 || h == h2 ) { count--; continue; } } results[j++] = filename; } /* only difference between first2 and next2 * responses is subCommand so let's recycle */ response.subCommand = SmbComTransaction.TRANS2_FIND_NEXT2; while( response.isEndOfSearch == false && response.searchCount > 0 ) { sendTransaction( new Trans2FindNext2( sid, response.resumeKey, response.lastName ), response ); count += response.searchCount; if( count > results.length ) { String[] tmp = results; results = new String[Math.max( results.length * 2, count )]; System.arraycopy( tmp, 0, results, 0, j ); } i = 0; while( j < count ) { filename = response.results[i++].filename; if( filename.length() < 3 ) { int h = filename.hashCode(); if( h == h1 || h == h2 ) { count--; continue; } } results[j++] = filename; } } send( new SmbComFindClose2( sid ), blank_resp ); if( results.length != count ) { String[] tmp = results; results = new String[count]; System.arraycopy( tmp, 0, results, 0, count ); } return results; } /** * 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( "*" ); } /** * The CIFS protocol provides for DOS "wildcards" to be used as * a performance enhancement. The client does not have to filter * the names ane 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 { if( url.toString().lastIndexOf( '/' ) != ( url.toString().length() - 1 )) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRlistFiles, url.toString() + " directory must end with '/'" ); } try { if( url.getHost().length() == 0 ) { connect0(); NetServerEnum2Response response = new NetServerEnum2Response(); sendTransaction( new NetServerEnum2( tree.session.transport.server.oemDomainName, NetServerEnum2.SV_TYPE_DOMAIN_ENUM ), response ); if( response.status != SmbException.NERR_Success && response.status != SmbException.ERROR_MORE_DATA ) { throw new SmbException( SmbException.ERRRAP, response.status, response.toString() ); } SmbFile[] ret = new SmbFile[response.entriesReturned]; for( int i = 0; i < response.entriesReturned; i++ ) { ret[i] = new SmbFile( this, response.results[i].name, TYPE_WORKGROUP, ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L ); } //System.err.println( "ret=" + ret.length + ",ret[0]=" + ret[0] + ",name=" + response.results[0].name ); return ret; } else if( share == null ) { if( getType() == TYPE_WORKGROUP ) { NetServerEnum2Response response = new NetServerEnum2Response(); sendTransaction( new NetServerEnum2( url.getHost(), NetServerEnum2.SV_TYPE_ALL ), response ); if( response.status != SmbException.NERR_Success && response.status != SmbException.ERROR_MORE_DATA ) { throw new SmbException( SmbException.ERRRAP, response.status ); } SmbFile[] ret = new SmbFile[response.entriesReturned]; for( int i = 0; i < response.entriesReturned; i++ ) { ret[i] = new SmbFile( this, response.results[i].name, TYPE_SERVER, ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L ); } return ret; } else { NetShareEnumResponse response = new NetShareEnumResponse(); sendTransaction( new NetShareEnum(), response ); if( response.status != SmbException.NERR_Success && response.status != SmbException.ERROR_MORE_DATA ) { throw new SmbException( SmbException.ERRRAP, response.status ); } SmbFile[] ret = new SmbFile[response.entriesReturned]; for( int i = 0; i < response.entriesReturned; i++ ) { int shareType = response.results[i].type; switch( shareType ) { case 1: shareType = TYPE_PRINTER; break; case 3: shareType = TYPE_NAMED_PIPE; break; default: shareType = TYPE_SHARE; break; } ret[i] = new SmbFile( this, response.results[i].netName, shareType, ATTR_READONLY | ATTR_DIRECTORY, 0L, 0L ); } return ret; } } else { return listFiles( getUncPath0(), wildcard ); } } catch( UnknownHostException uhe ) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRunknownHost, url.toString() ); } catch( MalformedURLException mue ) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRlistFiles, url.toString() ); } } SmbFile[] listFiles( String dirPath, String wildcard ) throws SmbException { int sid, count, i, j; SmbFile[] results; String base, filename; Log.println( Log.WARNINGS, "smb find warning", " find with path=" + dirPath ); Trans2FindFirst2Response response = new Trans2FindFirst2Response(); if( dirPath.equals( "\\" )) { filename = "\\" + wildcard; } else { filename = dirPath + "\\" + wildcard; } sendTransaction( new Trans2FindFirst2( filename ), response ); sid = response.sid; count = response.searchCount; j = 0; results = new SmbFile[Math.max( 16, count )]; int h1 = new String( "." ).hashCode(); int h2 = new String( ".." ).hashCode(); i = 0; try { while( j < count ) { filename = response.results[i].filename; if( filename.length() < 3 ) { int h = filename.hashCode(); if( h == h1 || h == h2 ) { count--; i++; continue; } } results[j++] = new SmbFile( this, filename, TYPE_FILESYSTEM, response.results[i].extFileAttributes, response.results[i].lastWriteTime, response.results[i].endOfFile ); i++; } /* only difference between first2 and next2 * responses is subCommand so let's recycle */ response.subCommand = SmbComTransaction.TRANS2_FIND_NEXT2; while( response.isEndOfSearch == false && response.searchCount > 0 ) { sendTransaction( new Trans2FindNext2( sid, response.resumeKey, response.lastName ), response ); count += response.searchCount; if( count > results.length ) { SmbFile[] tmp = results; results = new SmbFile[Math.max( results.length * 2, count )]; System.arraycopy( tmp, 0, results, 0, j ); } i = 0; while( j < count ) { filename = response.results[i].filename; if( filename.length() < 3 ) { int h = filename.hashCode(); if( h == h1 || h == h2 ) { count--; continue; } } results[j++] = new SmbFile( this, filename, TYPE_FILESYSTEM, response.results[i].extFileAttributes, response.results[i].lastWriteTime, response.results[i].endOfFile ); i++; } } } catch( UnknownHostException uhe ) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRunknownHost, url.toString() ); } catch( MalformedURLException mue ) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRlistFiles, "Malformed URL: " + url.toString() ); } send( new SmbComFindClose2( sid ), blank_resp ); if( results.length != count ) { SmbFile[] tmp = results; results = new SmbFile[count]; System.arraycopy( tmp, 0, results, 0, count ); } return results; } /** * 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). * * @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( SmbException.ERRDOS, SmbException.ERRnoaccess, "Cannot rename shares, servers, workgroups, or domains" ); } connect0(); dest.connect0(); if( tree != dest.tree ) { throw new SmbException( SmbException.ERRDOS, SmbException.ERRnoaccess, "Cannot rename file to a different share" ); } Log.println( Log.WARNINGS, "smb rename warning", " oldFileName=" + unc + ",newFileName=" + dest.unc ); attrExpiration = sizeExpiration = 0; /* * Rename Request / Response */ send( new SmbComRename( unc, dest.unc ), blank_resp ); } class WriterThread extends Thread { byte[] b; int n, off; boolean ready = true; SmbFile dest; SmbException e = null; SmbComWriteAndX req = new SmbComWriteAndX(); SmbComWriteAndXResponse resp = new SmbComWriteAndXResponse(); WriterThread() { super( "JCIFS-WriterThread" ); } 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( ;; ) { ready = true; while( ready ) { wait(); } if( n == -1 ) { return; } req.setParam( dest.fid, off, n, b, 0, n ); dest.send( req, resp ); notify(); } } catch( SmbException e ) { this.e = e; } catch( Exception x ) { this.e = new SmbException( SmbException.ERRCLI, SmbException.ERRioe, x.getMessage() ); } 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; lastModified = 0L; isExists = false; Info info = queryPath( getUncPath0(), Trans2QueryPathInformationResponse.SMB_QUERY_FILE_BASIC_INFO ); attributes = info.getAttributes(); 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(); } catch( SmbException se ) { if( se.getErrorCode() != SmbException.ERRnoaccess && se.getErrorCode() != SmbException.ERRexists ) { throw se; } } files = listFiles(); try { for( i = 0; i < files.length; i++ ) { ndest = new SmbFile( dest, files[i].getName(), files[i].type, files[i].attributes, files[i].lastModified, files[i].size ); files[i].copyTo0( ndest, b, bsize, w, req, resp ); } } catch( UnknownHostException uhe ) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRunknownHost, url.toString() ); } catch( MalformedURLException mue ) { throw new SmbException( SmbException.ERRCLI, SmbException.ERRlistFiles, url.toString() ); } } else { int off; open( SmbFile.O_RDONLY ); dest.open( SmbFile.O_CREAT | SmbFile.O_WRONLY | SmbFile.O_TRUNC ); 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( SmbException.ERRCLI, SmbException.ERRioe, ie.getMessage() ); } } 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.close( lastModified - tree.session.transport.server.serverTimeZone * 60 * 1000 ); close(); } } /** * This method will copy the file or directory and it's subcontents represented by this SmbFile 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 and files copied to NT 4.0 will not preserve file modification times. */ 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( getUncPath0().length() == 1 || dest.getUncPath0().length() == 1 ) { throw new SmbException( SmbException.ERRDOS, SmbException.ERRnoaccess, "Cannot copyTo workgroups, servers, or shares" ); } req = new SmbComReadAndX(); resp = new SmbComReadAndXResponse(); w = new WriterThread(); w.setDaemon( true ); w.start(); connect0(); dest.connect0(); bsize = Math.min( Math.min( tree.session.transport.rcv_buf_size - 70, dest.tree.session.transport.server.maxBufferSize - 70 ), Math.min( tree.session.transport.snd_buf_size - 70, dest.tree.session.transport.server.maxBufferSize - 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 the resource or sub-resource * is marked read-only or is locked the operation will fail. There is currently no functionality to set the attributes of a file, break locks, or disconnect users. * * @throws SmbException */ public void delete() throws SmbException { delete( getUncPath0() ); } void delete( String fileName ) throws SmbException { if( getUncPath0().length() == 1 ) { throw new SmbException( SmbException.ERRDOS, SmbException.ERRnoaccess ); } /* * Delete or Delete Directory Request / Response */ if( isDirectory() ) { /* Recursively delete directory contents */ SmbFile[] l = listFiles( fileName, "*" ); for( int i = 0; i < l.length; i++ ) { l[i].delete(); } Log.println( Log.WARNINGS, "smb delete directory warning", " fileName=" + fileName ); send( new SmbComDeleteDirectory( fileName ), blank_resp ); } else { Log.println( Log.WARNINGS, "smb delete warning", " fileName=" + fileName ); 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. */ 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. */ 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 successfull, 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. * * @throws SmbException */ public void mkdir() throws SmbException { if( getUncPath0().length() == 1 ) { throw new SmbException( SmbException.ERRDOS, SmbException.ERRnoaccess ); } Log.println( Log.WARNINGS, "smb create directory warning", " directoryName=" + getUncPath0() ); /* * Create Directory Request / Response */ send( new SmbComCreateDirectory( getUncPath0() ), blank_resp ); attrExpiration = sizeExpiration = 0; } /** * Creates a directory with the path specified by this SmbFile and any parent directories if necessary. * For this method to be successfull, 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. * * @throws SmbException */ public void mkdirs() throws SmbException { SmbFile parent; try { parent = new SmbFile( new URL( null, getParent(), Handler.SMB_HANDLER )); } catch( IOException ioe ) { return; } if( parent.exists() == false ) { parent.mkdirs(); } mkdir(); } /** * 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. * * @depricated Use getURL() instead * * @return A new {@link java.net.URL} for this SmbFile */ 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 */ public int hashCode() { return url.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 */ public boolean equals( Object obj ) { //HERE 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 */ public String toString() { return url.toString(); } }