diff --git a/src/mslinks/ShellLink.java b/src/mslinks/ShellLink.java index 5bcbff9..f9139e9 100644 --- a/src/mslinks/ShellLink.java +++ b/src/mslinks/ShellLink.java @@ -269,9 +269,9 @@ public String resolveTarget() { for (ItemID i : idlist) { if (i.getType() == ItemID.TYPE_DRIVE || i.getType() == ItemID.TYPE_DRIVE_OLD) path = i.getName(); - else if (i.getType() == ItemID.TYPE_DIRECTORY) + else if (i.getType() == ItemID.TYPE_DIRECTORY || i.getType() == ItemID.TYPE_DIRECTORY_OLD) path += i.getName() + File.separator; - else if (i.getType() == ItemID.TYPE_FILE) + else if (i.getType() == ItemID.TYPE_FILE || i.getType() == ItemID.TYPE_FILE_OLD) path += i.getName(); } return path; diff --git a/src/mslinks/data/ItemID.java b/src/mslinks/data/ItemID.java index ae4106f..e98c912 100644 --- a/src/mslinks/data/ItemID.java +++ b/src/mslinks/data/ItemID.java @@ -18,6 +18,8 @@ import io.ByteWriter; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; import java.util.regex.Pattern; import mslinks.Serializable; @@ -36,6 +38,8 @@ public class ItemID implements Serializable { private static final int EXT_VERSION_WIN8 = 9; // same for win10 public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_FILE_OLD = 0x36; + public static final int TYPE_DIRECTORY_OLD = 0x35; public static final int TYPE_FILE = 0x32; public static final int TYPE_DIRECTORY = 0x31; public static final int TYPE_DRIVE_OLD = 0x23; @@ -46,13 +50,11 @@ public class ItemID implements Serializable { private int size; private String shortname, longname; private GUID clsid; - private boolean hasExtension; private byte[] data; public ItemID() { shortname = ""; longname = ""; - hasExtension = true; } public ItemID(byte[] d) { @@ -61,31 +63,46 @@ public ItemID(byte[] d) { public ItemID(ByteReader br, int maxSize) throws IOException, ShellLinkException { int pos = br.getPosition(); + int endPos = pos + maxSize; type = br.read(); if (type == TYPE_DRIVE || type == TYPE_DRIVE_OLD) { setName(br.readString(maxSize - 1)); - br.seek(pos + maxSize - br.getPosition()); + br.seek(endPos - br.getPosition()); + } else if (type == TYPE_FILE_OLD || type == TYPE_DIRECTORY_OLD) { + br.read(); // unknown + size = (int)br.read4bytes(); + br.read4bytes(); //last modified + br.read2bytes(); // folder attributes + longname = br.readUnicodeString(endPos - br.getPosition()); + shortname = br.readString(endPos - br.getPosition()); + br.seek(endPos - br.getPosition()); } else if (type == TYPE_FILE || type == TYPE_DIRECTORY) { br.read(); // unknown size = (int)br.read4bytes(); br.read4bytes(); //last modified br.read2bytes(); // folder attributes - shortname = br.readString(13); - if (((br.getPosition() - pos) & 1) != 0) - br.read(); - if (pos + maxSize - br.getPosition() < 2) { + shortname = br.readString(endPos - br.getPosition()); + if (isLongFilename(shortname)) { + longname = shortname; + shortname = br.readString(endPos - br.getPosition()); br.seek(pos + maxSize - br.getPosition()); - hasExtension = false; + return; + } + if (endPos - br.getPosition() <= 2) { + longname = shortname; + br.seek(endPos - br.getPosition()); return; } + if (((br.getPosition() - pos) & 1) != 0) + br.read(); pos = br.getPosition(); int extSize = (int)br.read2bytes(); int extensionVersion = (int)br.read2bytes(); br.read4bytes(); // unknown br.read4bytes(); // date created br.read4bytes(); // last accessed - // unknown blocks depended on os version + // unknown blocks depending on os version switch (extensionVersion) { case EXT_VERSION_WINXP: br.seek(4); break; case EXT_VERSION_VISTA: br.seek(22); break; @@ -110,28 +127,36 @@ public void serialize(ByteWriter bw) throws IOException { bw.writeBytes(data); return; } + + boolean unicodeName = longname != null && !longname.equals(shortname); int pos = bw.getPosition(); - bw.write(type); + //bw.write(type); int attr = 0; switch (type) { case TYPE_CLSID: + bw.write(type); bw.write(0); - clsid.serialize(bw); + clsid.serialize(bw); return; case TYPE_DRIVE: case TYPE_DRIVE_OLD: + bw.write(type); byte[] b = getName().getBytes(); bw.write(b); for (int i=0; i<22-b.length; i++) bw.write(0); return; case TYPE_DIRECTORY: + case TYPE_DIRECTORY_OLD: + bw.write(unicodeName ? TYPE_DIRECTORY_OLD : TYPE_DIRECTORY); bw.write(0); bw.write4bytes(0); attr = 0x10; break; case TYPE_FILE: + case TYPE_FILE_OLD: + bw.write(unicodeName ? TYPE_FILE_OLD : TYPE_FILE); bw.write(0); bw.write4bytes(size); break; @@ -139,22 +164,33 @@ public void serialize(ByteWriter bw) throws IOException { bw.write4bytes(0); // last modified bw.write2bytes(attr); + // use simple old format without extension used in versions before xp + // it seems like there are no problems on newer systems, also it supports long unicode names on old ones + if (unicodeName) { + bw.writeUnicodeString(longname, true); + bw.writeBytes(shortname.getBytes()); + bw.write(0); + } else { + bw.writeBytes(shortname.getBytes()); + bw.write(0); + bw.write(0); + } + + /* bw.writeBytes(shortname.getBytes()); bw.write(0); if (((bw.getPosition() - pos) & 1) != 0) bw.write(0); - if (!hasExtension) - return; - bw.write2bytes(2 + 2 + ub1.length + 4 + 4 + ub2.length + 4 + (longname.length() + 1) * 2 + 2); bw.write2bytes(EXT_VERSION_WINXP); bw.writeBytes(ub1); bw.write4bytes(0); // date created bw.write4bytes(0); // last accessed bw.writeBytes(ub2); - bw.write4bytes(0); // unknown block depended on os version (always use WinXP) + bw.write4bytes(0); // unknown block depending on os version (always use WinXP) bw.writeUnicodeString(longname, true); bw.write2bytes((shortname.length() & ~1) + 16); + */ } public String getName() { @@ -170,20 +206,9 @@ public ItemID setName(String s) throws ShellLinkException { if (type == TYPE_FILE || type == TYPE_DIRECTORY) { if (s.contains("\\")) throw new ShellLinkException("wrong name"); - + longname = s; - - String name, ext = ""; - int dot = s.lastIndexOf('.'); - if (dot != -1) { - name = s.substring(0, dot); - ext = s.substring(name.length()); - } else name = s; - - if (name.length() > 8) - name = name.substring(0, 6) + "~1"; - - shortname = name + ext; + shortname = isLongFilename(s) ? generateShortName(s) : s; } if (type == TYPE_DRIVE || type == TYPE_DRIVE_OLD) { if (Pattern.matches("\\w+:\\\\", s)) @@ -219,5 +244,52 @@ public ItemID setType(int t) throws ShellLinkException { throw new ShellLinkException("wrong type"); } + private static boolean isLongFilename( String filename ) + { + if( filename.charAt( 0 ) == '.' || filename.charAt( filename.length() - 1 ) == '.' ) + return true; + + if( !filename.matches( "^\\p{ASCII}+$" ) ) + return true; + + // no matter whether it is file or directory + int dotIdx = filename.lastIndexOf( '.' ); + String baseName = dotIdx == -1 ? filename : filename.substring( 0, dotIdx ); + String ext = dotIdx == -1 ? "" : filename.substring( dotIdx + 1 ); + + String wrongSymbolsPattern = ".*[\\.\"\\/\\\\\\[\\]:;=, ]+.*"; + return baseName.length() > 8 || ext.length() > 3 || baseName.matches( wrongSymbolsPattern ) || ext.matches( wrongSymbolsPattern ); + } + + private static String generateShortName( String longname ) + { + // assume that it is actually long, don't check it again + longname = longname.replaceAll( "\\.$|^\\.", "" ); + + int dotIdx = longname.lastIndexOf( '.' ); + String baseName = dotIdx == -1 ? longname : longname.substring( 0, dotIdx ); + String ext = dotIdx == -1 ? "" : longname.substring( dotIdx + 1 ); + + ext = ext.replaceAll( " ", "" ).replaceAll( "[\\.\"\\/\\\\\\[\\]:;=,\\+]", "_" ); + ext = ext.substring( 0, Math.min( 3, ext.length() ) ); + + baseName = baseName.replaceAll( " ", "" ).replaceAll( "[\\.\"\\/\\\\\\[\\]:;=,\\+]", "_" ); + baseName = baseName.substring( 0, Math.min( 6, baseName.length() ) ); + + // well, for same short names we should use "~2", "~3" and so on, + // but actual index is generated by os while creating a file and stored in filesystem + // so it is not possible to get actual one + StringBuilder shortname = new StringBuilder( baseName + "~1" + ( ext.isEmpty() ? "" : "." + ext ) ); + + // i have no idea how non-asci symbols are converted in dos names + CharsetEncoder asciiEncoder = Charset.forName( "US-ASCII" ).newEncoder(); + for( int i = 0; i < shortname.length(); ++i ) + { + if( !asciiEncoder.canEncode( shortname.charAt( i ) ) ) + shortname.setCharAt( i, '_' ); + } + + return shortname.toString().toUpperCase(); + } }