Skip to content

Commit

Permalink
improved support for old versions of windows
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitriiShamrikov committed Sep 28, 2016
1 parent 6d7b8d0 commit bf032a8
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 30 deletions.
4 changes: 2 additions & 2 deletions src/mslinks/ShellLink.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
128 changes: 100 additions & 28 deletions src/mslinks/data/ItemID.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -110,51 +127,70 @@ 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;
}

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() {
Expand All @@ -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))
Expand Down Expand Up @@ -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();
}
}

0 comments on commit bf032a8

Please sign in to comment.