Skip to content

Commit

Permalink
implement changes based on PR feedback
Browse files Browse the repository at this point in the history
Polish code, remove spaces

Polish code, remove unused method

Revert last change

Improve design, rename, refactor some classes

Implement recursion limiter

Improve JUnit test, follow test framework default utilities

Polish code, fix typos, consistent variable names

Refactor JUnit test to support Jcif testing framework

Add support for resolving relative symlinks

Increase coverage of unit tests for relative symlinks
  • Loading branch information
GregBragg committed Sep 9, 2022
1 parent 024cfcf commit 1bf0060
Show file tree
Hide file tree
Showing 8 changed files with 460 additions and 194 deletions.
2 changes: 1 addition & 1 deletion src/main/java/jcifs/SmbResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public interface SmbResource extends AutoCloseable {
* @return <code>true</code> if this <code>SmbResource</code> is a symbolic link
* @throws CIFSException
*/
boolean isSymlink () throws CIFSException;
boolean isSymLink () throws CIFSException;


/**
Expand Down
131 changes: 41 additions & 90 deletions src/main/java/jcifs/internal/smb2/Smb2ErrorContextResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,128 +19,79 @@

import jcifs.internal.SMBProtocolDecodingException;
import jcifs.internal.util.SMBUtil;
import jcifs.util.Strings;

/**
* 2.2.2.1 SMB2 ERROR Context Response
*
* Defines methods to decode a SMB2 Error Context Response byte array.
* Currently only supports a Symbolic Link Error Response, however can
* be enhanced to support others.
*
* @author Gregory Bragg
*/
public class Smb2ErrorContextResponse {

private int errorDataLengthLength;
private int errorId;
public abstract class Smb2ErrorContextResponse {

private boolean absolutePath;
private String substituteName;
private String printName;
protected int errorDataLength;
protected int errorId;

/**
* 2.2.2.1 SMB2 ERROR Context Response
*
* For the SMB dialect 3.1.1, the servers format the error data as an array of SMB2 ERROR Context
* structures. Each error context is a variable-length structure that contains an identifier for
* the error context followed by the error data.
*
* Each SMB2 ERROR Context MUST start at an 8-byte aligned boundary relative to the start of the
* SMB2 ERROR Response. Otherwise, it SHOULD be formatted as specified in section 2.2.2.2.
*
* This method must be called first in any of the methods implemented by the subclasses.
*
* @param buffer
* @return The length, in bytes, of the response including the variable-length portion
* @throws Smb2ProtocolDecodingException
*/
public int readErrorContextResponse ( byte[] buffer ) throws SMBProtocolDecodingException {
protected int readErrorContextResponse(byte[] buffer) {
// start at the beginning of the 8-byte aligned boundary
// for the SMB2 ERROR Context structure
int bufferIndex = 0;

// ErrorDataLength (4 bytes)
this.errorDataLengthLength = SMBUtil.readInt4(buffer, bufferIndex);
this.errorDataLength = SMBUtil.readInt4(buffer, bufferIndex);
bufferIndex += 4;

// ErrorId (4 bytes)
// STATUS_STOPPED_ON_SYMLINK is always 0x00000000
this.errorId = SMBUtil.readInt4(buffer, bufferIndex);
if ( this.errorId != 0 ) {
throw new SMBProtocolDecodingException("ErrorId should be 0 for STATUS_STOPPED_ON_SYMLINK");
}
else {
bufferIndex += 4;
return this.readSymLinkErrorResponse( buffer, bufferIndex );
}
bufferIndex += 4;

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
* @param bufferIndex
* @return The length, in bytes, of the response including the variable-length portion
* and excluding SymLinkLength
* @return The length, in bytes, of the response
* @throws Smb2ProtocolDecodingException
*/
protected int readSymLinkErrorResponse ( byte[] buffer, int bufferIndex ) throws SMBProtocolDecodingException {
// SymLinkLength (4 bytes)
int symLinkLength = SMBUtil.readInt4(buffer, bufferIndex);
bufferIndex += 4;

// SymLinkErrorTag (4 bytes) (always 0x4C4D5953)
int symLinkErrorTag = SMBUtil.readInt4(buffer, bufferIndex);
if ( !Integer.toHexString(symLinkErrorTag).toUpperCase().equals("4C4D5953") ) {
throw new SMBProtocolDecodingException("SymLinkErrorTag should be 0x4C4D5953");
}
bufferIndex += 4;

// skip, not needed
bufferIndex += 4; // ReparseTag (4 bytes) (always 0xA000000C)
bufferIndex += 2; // ReparseDataLength (2 bytes)

// UnparsedPathLength (2 bytes)
int unparsedPathLength = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// SubstituteNameOffset (2 bytes)
int substituteNameOffset = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// SubstituteNameLength (2 bytes)
int substituteNameLength = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// PrintNameOffset (2 bytes)
int printNameOffset = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// PrintNameLength (2 bytes)
int printNameLength = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// Flags (4 bytes)
this.absolutePath = SMBUtil.readInt4(buffer, bufferIndex) == 0;
bufferIndex += 4;

// PathBuffer (variable), substitute name
this.substituteName = Strings.fromUNIBytes(buffer, substituteNameOffset + bufferIndex, substituteNameLength);

// PathBuffer (variable), print name that also identifies the target of the symbolic link
this.printName = Strings.fromUNIBytes(buffer, printNameOffset + bufferIndex, printNameLength);

return symLinkLength;
}
public abstract int readSymLinkErrorResponse ( byte[] buffer ) throws SMBProtocolDecodingException;

public int getErrorDataLengthLength () {
return this.errorDataLengthLength;
}

public int getErrorId () {
return this.errorId;
}

public boolean isAbsolutePath () {
return this.absolutePath;
}

public String getSubstituteName () {
return this.substituteName;
}

public String getPrintName () {
return this.printName;
}
/**
* 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;

}
152 changes: 152 additions & 0 deletions src/main/java/jcifs/internal/smb2/Smb2ErrorDataFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* © 2022 AgNO3 Gmbh & Co. KG
*
* 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.internal.smb2;

import java.util.List;

import jcifs.internal.SMBProtocolDecodingException;
import jcifs.internal.util.SMBUtil;
import jcifs.util.Strings;

/**
* 2.2.2.2 ErrorData format
*
* Defines methods to decode a SMB2 ErrorData format byte array.
*
* @author Gregory Bragg
*/
public class Smb2ErrorDataFormat extends Smb2ErrorContextResponse {

private boolean absolutePath;
private String substituteName;
private String printName;

@Override
public int readSymLinkErrorResponse ( byte[] buffer) throws SMBProtocolDecodingException {
int bufferIndex = super.readErrorContextResponse(buffer);
if ( this.errorId != 0 ) {
throw new SMBProtocolDecodingException("ErrorId should be 0 for STATUS_STOPPED_ON_SYMLINK");
}

// SymLinkLength (4 bytes)
int symLinkLength = SMBUtil.readInt4(buffer, bufferIndex);
bufferIndex += 4;

// SymLinkErrorTag (4 bytes) (always 0x4C4D5953)
int symLinkErrorTag = SMBUtil.readInt4(buffer, bufferIndex);
if ( !Integer.toHexString(symLinkErrorTag).toUpperCase().equals("4C4D5953") ) {
throw new SMBProtocolDecodingException("SymLinkErrorTag should be 0x4C4D5953");
}
bufferIndex += 4;

// ReparseTag (4 bytes) (always 0xA000000C)
int reparseTag = SMBUtil.readInt4(buffer, bufferIndex);
if ( !Integer.toHexString(reparseTag).toUpperCase().equals("A000000C") ) {
throw new SMBProtocolDecodingException("ReparseTag should be 0xA000000C");
}
bufferIndex += 4;

// ReparseDataLength (2 bytes)
int reparsedPathLength = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// UnparsedPathLength (2 bytes)
int unparsedPathLength = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// SubstituteNameOffset (2 bytes)
int substituteNameOffset = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// SubstituteNameLength (2 bytes)
int substituteNameLength = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// PrintNameOffset (2 bytes)
int printNameOffset = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// PrintNameLength (2 bytes)
int printNameLength = SMBUtil.readInt2(buffer, bufferIndex);
bufferIndex += 2;

// Flags (4 bytes) A 32-bit bit field that specifies whether the substitute is an absolute target path name
// or a path name relative to the directory containing the symbolic link.
// We only read the first 2 bytes to get the value, either 0x00000000 or 0x00000001 (0 or 1).
this.absolutePath = SMBUtil.readInt2(buffer, bufferIndex) == 0;
bufferIndex += 4;

// PathBuffer (variable), substitute name
this.substituteName = Strings.fromUNIBytes(buffer, substituteNameOffset + bufferIndex, substituteNameLength);

// PathBuffer (variable), print name that also identifies the target of the symbolic link
this.printName = Strings.fromUNIBytes(buffer, printNameOffset + bufferIndex, printNameLength);

return symLinkLength;
}


public String normalizeSymLinkPath(String path) {
List<String> parts = Strings.split(path, '\\');

for (int i = 0; i < parts.size(); ) {
String s = parts.get(i);
if (".".equals(s)) {
parts.remove(i);
} else if ("..".equals(s)) {
if (i > 0) {
parts.remove(i--);
}
parts.remove(i);
} else {
i++;
}
}

return Strings.join(parts, '\\');
}


public int getErrorDataLength () {
return this.errorDataLength;
}

public int getErrorId () {
return this.errorId;
}

public boolean isAbsolutePath () {
return this.absolutePath;
}

public String getSubstituteName () {
return this.substituteName;
}

public String getPrintName () {
return this.printName;
}


@Override
public int readShareRedirectErrorContextResponse(byte[] buffer)
throws SMBProtocolDecodingException {
throw new UnsupportedOperationException("Not implemented");
}

}
42 changes: 42 additions & 0 deletions src/main/java/jcifs/internal/util/RecursionLimiter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* © 2022 AgNO3 Gmbh & Co. KG
*
* 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.internal.util;

/**
* Import static and insert emerge() call into the beginning of any method in your code that can be
* deeply recursive. You can adjust maximum allowed recursion level via the maxLevel variable. The
* emerge() procedure will interrupt execution on a level greater than the value of that variable.
* You can switch off this behavior by setting maxLevel to 0.
*
* This solution is thread-safe because it doesn't use any counter at all.
*/
public class RecursionLimiter {
public static int maxLevel = 50;

public static void emerge() {
if (maxLevel == 0)
return;
try {
throw new IllegalStateException("Too deep, emerging");
} catch (IllegalStateException e) {
if (e.getStackTrace().length > maxLevel + 1)
throw e;
}
}

}
Loading

0 comments on commit 1bf0060

Please sign in to comment.