diff --git a/src/main/java/jcifs/Configuration.java b/src/main/java/jcifs/Configuration.java
index d316575..e049c06 100644
--- a/src/main/java/jcifs/Configuration.java
+++ b/src/main/java/jcifs/Configuration.java
@@ -787,4 +787,17 @@ public interface Configuration {
* @return whether to permit guest logins when user authentication is requested
*/
boolean isAllowGuestFallback ();
+
+
+ /**
+ * Property jcifs.smb.client.symlinkBehavior, defaults to THROW
+ *
+ * See {@link SymlinkBehavior}
+ *
+ * Currently, automatic symlink following is only supported when using
+ * SMB 3.1.1 and only for symlinks pointing to the same share.
+ *
+ * @return how to handle encountered symlinks
+ */
+ SymlinkBehavior getSymlinkBehavior();
}
diff --git a/src/main/java/jcifs/SymlinkBehavior.java b/src/main/java/jcifs/SymlinkBehavior.java
new file mode 100644
index 0000000..6e79049
--- /dev/null
+++ b/src/main/java/jcifs/SymlinkBehavior.java
@@ -0,0 +1,35 @@
+/*
+ * © 2023 Moritz Bechler
+ *
+ * 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;
+
+/**
+ * Symlink behavior
+ *
+ */
+public enum SymlinkBehavior {
+ /**
+ * Will throw a {@link jcifs.smb.SmbSymlinkException} containing the target information when
+ * encountering a symlink
+ */
+ THROW,
+
+ /**
+ * Automatically resolve symlinks that are located on the same share
+ */
+ RESOLVE_SAME_SHARE
+}
diff --git a/src/main/java/jcifs/config/BaseConfiguration.java b/src/main/java/jcifs/config/BaseConfiguration.java
index b8d9cd1..0fc4699 100644
--- a/src/main/java/jcifs/config/BaseConfiguration.java
+++ b/src/main/java/jcifs/config/BaseConfiguration.java
@@ -32,15 +32,10 @@
import java.util.StringTokenizer;
import java.util.TimeZone;
+import jcifs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import jcifs.CIFSException;
-import jcifs.Configuration;
-import jcifs.DialectVersion;
-import jcifs.ResolverType;
-import jcifs.SmbConstants;
-
/**
* @author mbechler
@@ -140,7 +135,7 @@ public class BaseConfiguration implements Configuration {
protected String guestUsername = "GUEST";
protected String guestPassword = "";
protected boolean allowGuestFallback = false;
-
+ protected SymlinkBehavior symlinkBehavior = SymlinkBehavior.THROW;
/**
* @throws CIFSException
@@ -621,6 +616,11 @@ public boolean isAllowGuestFallback () {
}
+ @Override
+ public SymlinkBehavior getSymlinkBehavior() {
+ return this.symlinkBehavior;
+ }
+
@Override
public byte[] getMachineId () {
return this.machineId;
diff --git a/src/main/java/jcifs/config/DelegatingConfiguration.java b/src/main/java/jcifs/config/DelegatingConfiguration.java
index 4df9f7c..72f9801 100644
--- a/src/main/java/jcifs/config/DelegatingConfiguration.java
+++ b/src/main/java/jcifs/config/DelegatingConfiguration.java
@@ -26,6 +26,7 @@
import jcifs.Configuration;
import jcifs.DialectVersion;
import jcifs.ResolverType;
+import jcifs.SymlinkBehavior;
/**
@@ -914,4 +915,9 @@ public String getGuestPassword () {
public boolean isAllowGuestFallback () {
return this.delegate.isAllowGuestFallback();
}
+
+ @Override
+ public SymlinkBehavior getSymlinkBehavior() {
+ return this.delegate.getSymlinkBehavior();
+ }
}
diff --git a/src/main/java/jcifs/config/PropertyConfiguration.java b/src/main/java/jcifs/config/PropertyConfiguration.java
index a7c7b58..d89d00b 100644
--- a/src/main/java/jcifs/config/PropertyConfiguration.java
+++ b/src/main/java/jcifs/config/PropertyConfiguration.java
@@ -21,11 +21,7 @@
import java.net.InetAddress;
import java.util.Properties;
-import jcifs.CIFSException;
-import jcifs.Config;
-import jcifs.Configuration;
-import jcifs.DialectVersion;
-import jcifs.SmbConstants;
+import jcifs.*;
/**
@@ -145,6 +141,8 @@ public PropertyConfiguration ( Properties p ) throws CIFSException {
this.guestUsername = p.getProperty("jcifs.smb.client.guestUsername", "JCIFSGUEST");
this.guestPassword = p.getProperty("jcifs.smb.client.guestPassword", "");
+ this.symlinkBehavior = SymlinkBehavior.valueOf(p.getProperty("jcifs.smb.client.symlinkBehavior", "THROW"));
+
String minVer = p.getProperty("jcifs.smb.client.minVersion");
String maxVer = p.getProperty("jcifs.smb.client.maxVersion");
diff --git a/src/main/java/jcifs/internal/smb2/Smb2ErrorContextResponse.java b/src/main/java/jcifs/internal/smb2/Smb2ErrorContextResponse.java
index 81ec41f..9796970 100644
--- a/src/main/java/jcifs/internal/smb2/Smb2ErrorContextResponse.java
+++ b/src/main/java/jcifs/internal/smb2/Smb2ErrorContextResponse.java
@@ -64,34 +64,6 @@ protected int readErrorContextResponse ( byte[] buffer ) {
return bufferIndex;
}
- /**
- * 2.2.2.2.1 Symbolic Link Error Response
- *
- * The Symbolic Link Error Response is used to indicate that a symbolic link was encountered on
- * create; it describes the target path that the client MUST use if it requires to follow the
- * symbolic link. This structure is contained in the ErrorData section of the SMB2 ERROR
- * Response (section 2.2.2). This structure MUST NOT be returned in an SMB2 ERROR Response
- * unless the Status code in the header of that response is set to STATUS_STOPPED_ON_SYMLINK.
- *
- * @param buffer
- * @return The length, in bytes, of the response
- * @throws Smb2ProtocolDecodingException
- */
- public abstract int readSymLinkErrorResponse ( byte[] buffer ) throws SMBProtocolDecodingException;
- /**
- * 2.2.2.2.2 Share Redirect Error Context Response
- *
- * Servers which negotiate SMB 3.1.1 or higher can return this error context to a client in
- * response to a tree connect request with the SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER bit set
- * in the Flags field of the SMB2 TREE_CONNECT request. The corresponding Status code in the
- * SMB2 header of the response MUST be set to STATUS_BAD_NETWORK_NAME. The error context data is
- * formatted as follows.
- *
- * @param buffer
- * @return The length, in bytes, of the response
- * @throws SMBProtocolDecodingException
- */
- public abstract int readShareRedirectErrorContextResponse ( byte[] buffer ) throws SMBProtocolDecodingException;
}
diff --git a/src/main/java/jcifs/internal/smb2/Smb2ErrorDataFormat.java b/src/main/java/jcifs/internal/smb2/Smb2ErrorDataFormat.java
index 945f051..0eb2953 100644
--- a/src/main/java/jcifs/internal/smb2/Smb2ErrorDataFormat.java
+++ b/src/main/java/jcifs/internal/smb2/Smb2ErrorDataFormat.java
@@ -35,7 +35,19 @@ public class Smb2ErrorDataFormat extends Smb2ErrorContextResponse {
private String substituteName;
private String printName;
- @Override
+ /**
+ * 2.2.2.2.1 Symbolic Link Error Response
+ *
+ * The Symbolic Link Error Response is used to indicate that a symbolic link was encountered on
+ * create; it describes the target path that the client MUST use if it requires to follow the
+ * symbolic link. This structure is contained in the ErrorData section of the SMB2 ERROR
+ * Response (section 2.2.2). This structure MUST NOT be returned in an SMB2 ERROR Response
+ * unless the Status code in the header of that response is set to STATUS_STOPPED_ON_SYMLINK.
+ *
+ * @param buffer
+ * @return The length, in bytes, of the response
+ * @throws SMBProtocolDecodingException
+ */
public int readSymLinkErrorResponse ( byte[] buffer ) throws SMBProtocolDecodingException {
int bufferIndex = super.readErrorContextResponse(buffer);
if ( this.errorId != 0 ) {
@@ -133,7 +145,21 @@ public String getPrintName () {
}
- @Override
+
+
+ /**
+ * 2.2.2.2.2 Share Redirect Error Context Response
+ *
+ * Servers which negotiate SMB 3.1.1 or higher can return this error context to a client in
+ * response to a tree connect request with the SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER bit set
+ * in the Flags field of the SMB2 TREE_CONNECT request. The corresponding Status code in the
+ * SMB2 header of the response MUST be set to STATUS_BAD_NETWORK_NAME. The error context data is
+ * formatted as follows.
+ *
+ * @param buffer
+ * @return The length, in bytes, of the response
+ * @throws SMBProtocolDecodingException
+ */
public int readShareRedirectErrorContextResponse ( byte[] buffer )
throws SMBProtocolDecodingException {
throw new UnsupportedOperationException("Not implemented");
diff --git a/src/main/java/jcifs/internal/smb2/Smb2SymLinkResolver.java b/src/main/java/jcifs/internal/smb2/Smb2SymLinkResolver.java
index 0852aec..5893b88 100644
--- a/src/main/java/jcifs/internal/smb2/Smb2SymLinkResolver.java
+++ b/src/main/java/jcifs/internal/smb2/Smb2SymLinkResolver.java
@@ -17,14 +17,19 @@
*/
package jcifs.internal.smb2;
-import java.nio.charset.Charset;
-import java.util.List;
-
+import jcifs.SymlinkBehavior;
+import jcifs.internal.SMBProtocolDecodingException;
+import jcifs.internal.smb2.create.Smb2CreateResponse;
+import jcifs.smb.SmbSymlinkException;
+import jcifs.smb.SmbTreeHandleInternal;
+import jcifs.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import jcifs.internal.SMBProtocolDecodingException;
-import jcifs.util.Strings;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Locale;
/**
* Defines methods to resolve a symlink target path.
@@ -80,13 +85,13 @@ private String resolveSymLinkTarget ( String originalFileName ) {
private String getSymLinkParsedPath ( String fileName, int unparsedPathLength ) {
- byte[] fileNameBytes = fileName.getBytes(Charset.forName("UTF-16LE"));
+ byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_16LE);
return new String(fileNameBytes, 0, fileNameBytes.length - unparsedPathLength, Charset.forName("UTF-16LE"));
}
private String getSymLinkUnparsedPath ( String fileName, int unparsedPathLength ) {
- byte[] fileNameBytes = fileName.getBytes(Charset.forName("UTF-16LE"));
+ byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_16LE);
return new String(fileNameBytes, fileNameBytes.length - unparsedPathLength, unparsedPathLength, Charset.forName("UTF-16LE"));
}
@@ -111,4 +116,40 @@ private String normalizePath ( String path ) {
return Strings.join(parts, '\\');
}
+ public String processSymlinkError(SmbTreeHandleInternal th, Smb2CreateResponse cr) throws SMBProtocolDecodingException, SmbSymlinkException {
+ String targetPath = this.parseSymLinkErrorData(cr.getFileName(), cr.getErrorData());
+
+ if (SymlinkBehavior.THROW == th.getConfig().getSymlinkBehavior()) {
+ throw new SmbSymlinkException(targetPath, th.getSession().getContext());
+ }
+
+ String lcaseTgt = targetPath.toLowerCase(Locale.ROOT);
+ if (lcaseTgt.startsWith("\\??\\unc\\")) {
+ // this symlink references a UNC path, this can only be (easily) handled
+ // if the target resides on the share and we simply can adjust the request
+ // path. For more extensive support, the tree connection needs to be
+ // switched.
+
+ // cut off \\??\\unc\\ prefix
+ targetPath = targetPath.substring(8);
+ String curShareUnc = th.getRemoteHostName().toLowerCase(Locale.ROOT) +
+ "\\" + th.getConnectedShare().toLowerCase(Locale.ROOT) + "\\";
+
+ if ( !targetPath.startsWith(curShareUnc)) {
+ // TODO: convert to URL
+ throw new SmbSymlinkException(targetPath, th.getSession().getContext());
+ }
+
+ // return path below share
+ return targetPath.substring(curShareUnc.length());
+ } else if (targetPath.startsWith("\\??\\")) {
+ // this is a link to local file, cut off prefix \??\
+ // handling is up to the user
+ // TODO: convert to file URL?
+ throw new SmbSymlinkException(targetPath.substring(4), th.getSession().getContext());
+ } else {
+ // this is a relative symlink
+ return targetPath;
+ }
+ }
}
diff --git a/src/main/java/jcifs/smb/DirFileEntryEnumIterator2.java b/src/main/java/jcifs/smb/DirFileEntryEnumIterator2.java
index 92a27aa..9a9b576 100644
--- a/src/main/java/jcifs/smb/DirFileEntryEnumIterator2.java
+++ b/src/main/java/jcifs/smb/DirFileEntryEnumIterator2.java
@@ -18,16 +18,7 @@
package jcifs.smb;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import jcifs.CIFSException;
-import jcifs.Configuration;
-import jcifs.DialectVersion;
-import jcifs.ResourceNameFilter;
-import jcifs.SmbConstants;
-import jcifs.SmbResource;
-import jcifs.internal.SMBProtocolDecodingException;
+import jcifs.*;
import jcifs.internal.smb2.Smb2SymLinkResolver;
import jcifs.internal.smb2.create.Smb2CloseRequest;
import jcifs.internal.smb2.create.Smb2CreateRequest;
@@ -35,6 +26,8 @@
import jcifs.internal.smb2.info.Smb2QueryDirectoryRequest;
import jcifs.internal.smb2.info.Smb2QueryDirectoryResponse;
import jcifs.internal.util.RecursionLimiter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
@@ -92,7 +85,7 @@ protected FileEntry open () throws CIFSException {
/**
*
- * @param path
+ * @param uncPath
* @return
* @throws CIFSException
*/
@@ -125,24 +118,15 @@ private FileEntry open ( String uncPath ) throws CIFSException {
// We hit a symbolic link, parse the error data and resend for the 'real' directory or file path
if (cr != null && cr.isReceived() && cr.getStatus() == NtStatus.NT_STATUS_STOPPED_ON_SYMLINK) {
-
- if (config.getMinimumVersion() != DialectVersion.SMB311) {
- throw new SMBProtocolDecodingException(
- "Configuration must be set to a minimum of version SMB 3.1.1 for property "
- + "'jcifs.smb.client.minVersion' to resolve symbolic link target path");
+ if (!th.getSession().unwrap(SmbSessionInternal.class).getTransport().unwrap(SmbTransportInternal.class).getSelectedDialect().atLeast(DialectVersion.SMB311)) {
+ // symlink error context information is only available since SMB3.1.1
+ // TODO: might be able to get the target information via some IOCTL for earlier versions?
+ throw e;
}
-
+ Smb2SymLinkResolver resolver = new Smb2SymLinkResolver();
try {
- Smb2SymLinkResolver resolver = new Smb2SymLinkResolver();
- String targetPath = resolver.parseSymLinkErrorData(cr.getFileName(), cr.getErrorData());
-
- if (targetPath.startsWith("\\??\\")) {
- throw new SMBProtocolDecodingException("SymLink target is a local path: " + targetPath);
- } else {
- return open(targetPath);
- }
- }
- catch ( CIFSException | RuntimeException e3 ) {
+ return open(resolver.processSymlinkError(th, cr));
+ } catch ( CIFSException | RuntimeException e3 ) {
log.error("Exception thrown while processing symbolic link error data", e3);
e.addSuppressed(e3);
}
diff --git a/src/main/java/jcifs/smb/SmbFile.java b/src/main/java/jcifs/smb/SmbFile.java
index b94cefd..4be0096 100644
--- a/src/main/java/jcifs/smb/SmbFile.java
+++ b/src/main/java/jcifs/smb/SmbFile.java
@@ -697,22 +697,20 @@ else if ( ( flags & SmbConstants.O_CREAT ) == O_CREAT ) {
// We hit a symbolic link, parse the error data and resend for the 'real' directory or file path
if (resp != null && resp.isReceived() && resp.getStatus() == NtStatus.NT_STATUS_STOPPED_ON_SYMLINK) {
this.isSymLink = true;
-
- if (config.getMinimumVersion() != DialectVersion.SMB311) {
- throw new SMBProtocolDecodingException(
- "Configuration must be set to a minimum of version SMB 3.1.1 for property "
- + "'jcifs.smb.client.minVersion' to resolve symbolic link target path");
- }
-
try {
+ if (!h.getSession().unwrap(SmbSessionInternal.class).getTransport().unwrap(SmbTransportInternal.class).getSelectedDialect().atLeast(DialectVersion.SMB311)) {
+ // symlink error context information is only available since SMB3.1.1
+ // TODO: might be able to get the target information via some IOCTL for earlier versions?
+ throw e;
+ }
Smb2SymLinkResolver resolver = new Smb2SymLinkResolver();
- String targetPath = resolver.parseSymLinkErrorData(resp.getFileName(), resp.getErrorData());
- this.symLinkTargetPath = targetPath;
-
- if (targetPath.startsWith("\\??\\")) {
- throw new SMBProtocolDecodingException("SymLink target is a local path: " + targetPath);
- } else {
+ try {
+ String targetPath = resolver.parseSymLinkErrorData(resp.getFileName(), resp.getErrorData());
+ this.symLinkTargetPath = targetPath;
return openUnshared(targetPath, flags, access, sharing, attrs, options);
+ } catch ( CIFSException | RuntimeException e3 ) {
+ log.error("Exception thrown while processing symbolic link error data", e3);
+ e.addSuppressed(e3);
}
}
catch ( CIFSException | RuntimeException e2 ) {
@@ -720,11 +718,9 @@ else if ( ( flags & SmbConstants.O_CREAT ) == O_CREAT ) {
throw e2;
}
}
- else {
- log.error("Exception thrown while processing SMB2 request", e);
- throw e;
- }
+ throw e;
}
+
}
else if ( h.hasCapability(SmbConstants.CAP_NT_SMBS) ) {
SmbComNTCreateAndXResponse resp = new SmbComNTCreateAndXResponse(config);
@@ -1804,7 +1800,11 @@ protected T withOpen ( SmbTreeHandleImpl
@SuppressWarnings ( "unchecked" )
protected T withOpen ( SmbTreeHandleImpl th, int createDisposition, int createOptions, int fileAttributes,
int desiredAccess, int shareAccess, ServerMessageBlock2Request first, ServerMessageBlock2Request>... others ) throws CIFSException {
- Smb2CreateRequest cr = new Smb2CreateRequest(th.getConfig(), getUncPath());
+ return withOpen(th, getUncPath(), createDisposition, createOptions, fileAttributes, desiredAccess, shareAccess, first, others);
+ }
+
+ private T withOpen(SmbTreeHandleImpl th, String path, int createDisposition, int createOptions, int fileAttributes, int desiredAccess, int shareAccess, ServerMessageBlock2Request first,ServerMessageBlock2Request>... others) throws CIFSException {
+ Smb2CreateRequest cr = new Smb2CreateRequest(th.getConfig(), path);
try {
cr.setCreateDisposition(createDisposition);
cr.setCreateOptions(createOptions);
@@ -1818,7 +1818,7 @@ protected T withOpen ( SmbTreeHandleImpl
cr.chain(first);
cur = first;
- for ( ServerMessageBlock2Request> req : others ) {
+ for ( ServerMessageBlock2Request> req : others) {
cur.chain(req);
cur = req;
}
@@ -1859,15 +1859,21 @@ protected T withOpen ( SmbTreeHandleImpl
if ( createResp.isReceived() && createResp.getStatus() == NtStatus.NT_STATUS_STOPPED_ON_SYMLINK ) {
this.isSymLink = true;
- if (th.getConfig().getMinimumVersion() != DialectVersion.SMB311) {
- throw new SMBProtocolDecodingException(
- "Configuration must be set to a minimum of version SMB 3.1.1 for property "
- + "'jcifs.smb.client.minVersion' to resolve symbolic link target path");
- }
- // we hit a symbolic link, parse the error data to retrieve the target path
+ if (!th.getSession().unwrap(SmbSessionInternal.class).getTransport().unwrap(SmbTransportInternal.class).getSelectedDialect().atLeast(DialectVersion.SMB311)) {
+ // symlink error context information is only available since SMB3.1.1
+ // TODO: might be able to get the target information via some IOCTL for earlier versions?
+ throw e;
+ }
Smb2SymLinkResolver resolver = new Smb2SymLinkResolver();
- this.symLinkTargetPath = resolver.parseSymLinkErrorData(createResp.getFileName(), createResp.getErrorData());
+ try {
+ this.symLinkTargetPath = resolver.processSymlinkError(th, createResp);
+ // retry the request with the adjusted path
+ return withOpen(th, this.symLinkTargetPath, createDisposition, createOptions, fileAttributes, desiredAccess, shareAccess, first, others);
+ } catch ( CIFSException | RuntimeException e3 ) {
+ log.error("Exception thrown while processing symbolic link error data", e3);
+ e.addSuppressed(e3);
+ }
} else if ( createResp.isReceived() && createResp.getStatus() == NtStatus.NT_STATUS_OK ) {
// make sure that the handle is closed when one of the requests fails
th.send(new Smb2CloseRequest(th.getConfig(), createResp.getFileId()), RequestParam.NO_RETRY);
diff --git a/src/main/java/jcifs/smb/SmbSymlinkException.java b/src/main/java/jcifs/smb/SmbSymlinkException.java
new file mode 100644
index 0000000..29ce3cd
--- /dev/null
+++ b/src/main/java/jcifs/smb/SmbSymlinkException.java
@@ -0,0 +1,26 @@
+package jcifs.smb;
+
+import jcifs.CIFSContext;
+import jcifs.CIFSException;
+import jcifs.SmbResource;
+
+public class SmbSymlinkException extends SmbException {
+
+ private final String target;
+ private transient final CIFSContext context;
+
+ public SmbSymlinkException(String target, CIFSContext ctx) {
+ // TODO: transform to URL
+ this.target = target;
+ this.context = ctx;
+ }
+
+
+ public String getTarget() {
+ return target;
+ }
+
+ public SmbResource getResource() throws CIFSException {
+ return this.context.get(this.target);
+ }
+}
diff --git a/src/main/java/jcifs/smb/SmbTransportImpl.java b/src/main/java/jcifs/smb/SmbTransportImpl.java
index b170312..471a9d9 100644
--- a/src/main/java/jcifs/smb/SmbTransportImpl.java
+++ b/src/main/java/jcifs/smb/SmbTransportImpl.java
@@ -150,11 +150,6 @@ class SmbTransportImpl extends Transport implements SmbTransportInternal, SmbCon
}
- /**
- * {@inheritDoc}
- *
- * @see jcifs.util.transport.Transport#getResponseTimeout()
- */
@Override
protected int getResponseTimeout ( Request req ) {
if ( req instanceof CommonServerMessageBlockRequest ) {
@@ -213,6 +208,10 @@ public boolean hasCapability ( int cap ) throws SmbException {
return getNegotiateResponse().haveCapabilitiy(cap);
}
+ @Override
+ public DialectVersion getSelectedDialect() throws SmbException {
+ return getNegotiateResponse().getSelectedDialect();
+ }
/**
* @return the negotiated
diff --git a/src/main/java/jcifs/smb/SmbTransportInternal.java b/src/main/java/jcifs/smb/SmbTransportInternal.java
index 546935c..df12e16 100644
--- a/src/main/java/jcifs/smb/SmbTransportInternal.java
+++ b/src/main/java/jcifs/smb/SmbTransportInternal.java
@@ -20,11 +20,7 @@
import java.io.IOException;
-import jcifs.CIFSContext;
-import jcifs.CIFSException;
-import jcifs.DfsReferralData;
-import jcifs.SmbSession;
-import jcifs.SmbTransport;
+import jcifs.*;
/**
@@ -125,4 +121,11 @@ public interface SmbTransportInternal extends SmbTransport {
* @return number of inflight requests
*/
int getInflightRequests ();
+
+
+ /**
+ *
+ * @return negotiated SMB version
+ */
+ DialectVersion getSelectedDialect() throws SmbException;
}
diff --git a/src/test/java/jcifs/tests/SymLinkTest.java b/src/test/java/jcifs/tests/SymLinkTest.java
index 35377a5..128e355 100644
--- a/src/test/java/jcifs/tests/SymLinkTest.java
+++ b/src/test/java/jcifs/tests/SymLinkTest.java
@@ -227,7 +227,15 @@ private SmbFile createSession() throws MalformedURLException, CIFSException {
private long copyInputStreamToFile(InputStream is, File file) throws IOException {
try (OutputStream output = new FileOutputStream(file)) {
- return is.transferTo(output);
+ int read = 0;
+ long total = 0;
+ byte[] buffer = new byte[1024];
+
+ while ( (read = is.read(buffer)) != 0 ) {
+ output.write(buffer, 0, read);
+ total += read;
+ }
+ return total;
} catch (IOException ioe) {
log.error("copyInputStreamToFile error", ioe);
throw ioe;