diff --git a/src/io/ByteReader.java b/src/io/ByteReader.java index 495a8b676e09b99ace0d02134f800650abb09188..96a30fb5e4979a4c30a2d4a9527ae666e69055f2 100644 --- a/src/io/ByteReader.java +++ b/src/io/ByteReader.java @@ -44,6 +44,13 @@ public class ByteReader extends InputStream { return pos; } + public boolean seek(int n) throws IOException { + if (n == 0) return false; + for (int i=0; i<n; i++) + read(); + return true; + } + @Override public int read() throws IOException { pos++; @@ -133,6 +140,34 @@ public class ByteReader extends InputStream { else return b7 | (b6 << 8) | (b5 << 16) | (b4 << 24) | (b3 << 32) | (b2 << 40) | (b1 << 48) | (b0 << 56); } + + public String readString(int start, int size) throws IOException { + int sz = size + start - getPosition(); + if (sz == 0) return null; + byte[] buf = new byte[sz]; + int i = 0; + for (;; i++) { + int b = read(); + if (b == 0) break; + buf[i] = (byte)b; + } + if (i == 0) return null; + return new String(buf, 0, i); + } + + public String readUnicodeString(int start, int size) throws IOException { + int sz = (size + start - getPosition())>>1; + if (sz == 0) return null; + char[] buf = new char[sz]; + int i = 0; + for (;; i++) { + char c = (char)read2bytes(); + if (c == 0) break; + buf[i] = c; + } + if (i == 0) return null; + return new String(buf, 0, i); + } } enum Endianness { diff --git a/src/io/ByteWriter.java b/src/io/ByteWriter.java index ddbe31b6b24aee2b96054be3a9188144224fe1db..ad6ecf6f8140f7ca980d6898211a98312ec30c38 100644 --- a/src/io/ByteWriter.java +++ b/src/io/ByteWriter.java @@ -7,6 +7,8 @@ public class ByteWriter extends OutputStream { private OutputStream stream; private Endianness end = Endianness.LITTLE_ENDIAN; + private int pos = 0; + public ByteWriter(OutputStream out) { stream = out; @@ -38,8 +40,13 @@ public class ByteWriter extends OutputStream { return end == Endianness.LITTLE_ENDIAN; } + public int getPosition() { + return pos; + } + @Override public void write(int b) throws IOException { + pos++; stream.write(b); } @@ -137,4 +144,9 @@ public class ByteWriter extends OutputStream { write(b7); write(b6); write(b5); write(b4); write(b3); write(b2); write(b1); write(b0); } } + + public void writeBytes(byte[] b) throws IOException { + for (byte i : b) + write(i); + } } diff --git a/src/io/Bytes.java b/src/io/Bytes.java index 4abadf9d75631d92cfbddddd8af6763f01c99000..b322dc8e32a156af598b4bcd972f1610062907a2 100644 --- a/src/io/Bytes.java +++ b/src/io/Bytes.java @@ -39,12 +39,12 @@ public class Bytes { return (Bytes.l(b7) << 56) | (Bytes.l(b6) << 48) | (Bytes.l(b5) << 40) | (Bytes.l(b4) << 32) | (Bytes.l(b3) << 24) | (Bytes.l(b2) << 16) | (Bytes.l(b1) << 8) | Bytes.l(b0); } - static int i(byte b) { - return b & 0xff; - } - static long l(byte b) { return b & 0xffL; } + static int i(byte b) { + return b & 0xff; + } + } diff --git a/src/mslinks/LinkInfo.java b/src/mslinks/LinkInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..abb3c0536bd97041b9a4b6e9f7a377d94a1b338d --- /dev/null +++ b/src/mslinks/LinkInfo.java @@ -0,0 +1,232 @@ +package mslinks; + +import io.ByteReader; +import io.ByteWriter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; + +import mslinks.data.*; + +public class LinkInfo implements Serializable { + private LinkInfoFlags lif; + private VolumeID vid; + private String localBasePath; + private CNRLink cnrlink; + private String commonPathSuffix; + + public LinkInfo() { + createVolumeID(); + } + + public LinkInfo(ByteReader data) throws IOException, ShellLinkException { + int pos = data.getPosition(); + int size = (int)data.read4bytes(); + int hsize = (int)data.read4bytes(); + lif = new LinkInfoFlags(data); + int vidoffset = (int)data.read4bytes(); + int lbpoffset = (int)data.read4bytes(); + int cnrloffset = (int)data.read4bytes(); + int cpsoffset = (int)data.read4bytes(); + int lbpoffset_u = 0, cpfoffset_u = 0; + if (hsize >= 0x24) { + lbpoffset_u = (int)data.read4bytes(); + cpfoffset_u = (int)data.read4bytes(); + } + + if (lif.hasVolumeIDAndLocalBasePath()) { + data.seek(pos + vidoffset - data.getPosition()); + vid = new VolumeID(data); + data.seek(pos + lbpoffset - data.getPosition()); + localBasePath = data.readString(pos, size); + } + if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) { + data.seek(pos + cnrloffset - data.getPosition()); + cnrlink = new CNRLink(data); + data.seek(pos + cpsoffset - data.getPosition()); + commonPathSuffix = data.readString(pos, size); + } + if (lif.hasVolumeIDAndLocalBasePath() && lbpoffset_u != 0) { + data.seek(pos + lbpoffset_u - data.getPosition()); + localBasePath = data.readUnicodeString(pos, size); + } + if (lif.hasCommonNetworkRelativeLinkAndPathSuffix() && cpfoffset_u != 0) { + data.seek(pos + cpfoffset_u - data.getPosition()); + commonPathSuffix = data.readUnicodeString(pos, size); + } + + data.seek(pos + size - data.getPosition()); + } + + public void serialize(ByteWriter bw) throws IOException { + int pos = bw.getPosition(); + int hsize = 28; + CharsetEncoder ce = Charset.defaultCharset().newEncoder(); + if (localBasePath != null && !ce.canEncode(localBasePath) || commonPathSuffix != null && !ce.canEncode(commonPathSuffix)) + hsize += 8; + + byte[] vid_b = null, localBasePath_b = null, cnrlink_b = null, commonPathSuffix_b = null; + if (lif.hasVolumeIDAndLocalBasePath()) { + vid_b = toByteArray(vid, bw.isLitteEndian()); + localBasePath_b = localBasePath.getBytes(); + commonPathSuffix_b = new byte[0]; + } + if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) { + cnrlink_b = toByteArray(cnrlink, bw.isLitteEndian()); + commonPathSuffix_b = commonPathSuffix.getBytes(); + } + + int size = hsize + + (vid_b == null? 0 : vid_b.length) + + (localBasePath_b == null? 0 : localBasePath_b.length + 1) + + (cnrlink_b == null? 0 : cnrlink_b.length) + + commonPathSuffix_b.length + 1; + + if (hsize > 28) { + if (lif.hasVolumeIDAndLocalBasePath()) { + size += localBasePath.length() * 2 + 2; + size += 1; + } + if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) { + size += commonPathSuffix.length() * 2 + 2; + } else + size += 2; + } + + + bw.write4bytes(size); + bw.write4bytes(hsize); + lif.serialize(bw); + int off = hsize; + if (lif.hasVolumeIDAndLocalBasePath()) { + bw.write4bytes(off); // volumeid offset + off += vid_b.length; + bw.write4bytes(off); // localBasePath offset + off += localBasePath_b.length + 1; + bw.write4bytes(0); // CommonNetworkRelativeLinkOffset + bw.write4bytes(size - (hsize > 28 ? 4 : 1)); // fake commonPathSuffix offset + } + if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) { + bw.write4bytes(0); // volumeid offset + bw.write4bytes(0); // localBasePath offset + bw.write4bytes(off); // CommonNetworkRelativeLink offset + off += cnrlink_b.length; + bw.write4bytes(off); // commonPathSuffix + off += commonPathSuffix_b.length + 1; + } + if (hsize > 28) { + if (lif.hasVolumeIDAndLocalBasePath()) { + bw.write4bytes(off); // LocalBasePathOffsetUnicode + off += localBasePath.length() * 2 + 2; + bw.write4bytes(size - 2); // fake CommonPathSuffixUnicode offset + } else { + bw.write4bytes(0); + bw.write4bytes(off); // CommonPathSuffixUnicode offset + off += commonPathSuffix.length() * 2 + 2; + } + } + + if (lif.hasVolumeIDAndLocalBasePath()) { + bw.writeBytes(vid_b); + bw.writeBytes(localBasePath_b); + bw.write(0); + } + if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) { + bw.writeBytes(cnrlink_b); + bw.writeBytes(commonPathSuffix_b); + bw.write(0); + } + + if (hsize > 28) { + if (lif.hasVolumeIDAndLocalBasePath()) { + for (int i=0; i<localBasePath.length(); i++) + bw.write2bytes(localBasePath.charAt(i)); + bw.write2bytes(0); + } + if (lif.hasCommonNetworkRelativeLinkAndPathSuffix()) { + for (int i=0; i<commonPathSuffix.length(); i++) + bw.write2bytes(commonPathSuffix.charAt(i)); + bw.write2bytes(0); + } + } + + while (bw.getPosition() < pos + size) + bw.write(0); + } + + private byte[] toByteArray(Serializable o, boolean le) throws IOException { + ByteArrayOutputStream arr = new ByteArrayOutputStream(); + ByteWriter bt = new ByteWriter(arr); + if (le) bt.setLittleEndian(); + else bt.setBigEndian(); + o.serialize(bt); + return arr.toByteArray(); + } + + public VolumeID getVolumeID() { return vid; } + /** + * Creates VolumeID and LocalBasePath that is empty string, clears CommonNetworkRelativeLink and CommonPathSuffix + */ + public VolumeID createVolumeID() { + cnrlink = null; + commonPathSuffix = null; + lif.clearCommonNetworkRelativeLinkAndPathSuffix(); + + vid = new VolumeID(); + localBasePath = ""; + lif.setVolumeIDAndLocalBasePath(); + return vid; + } + + public String getLocalBasePath() { return localBasePath; } + /** + * Set LocalBasePath and creates new VolumeID (if it not exists), clears CommonNetworkRelativeLink and CommonPathSuffix. + * If s is null takes no effect + */ + public void setLocalBasePath(String s) { + if (s == null) return; + + localBasePath = s; + if (vid == null) vid = new VolumeID(); + lif.setVolumeIDAndLocalBasePath(); + + cnrlink = null; + commonPathSuffix = null; + lif.clearCommonNetworkRelativeLinkAndPathSuffix(); + } + + public CNRLink getCommonNetworkRelativeLink() { return cnrlink; } + /** + * Creates CommonNetworkRelativeLink and CommonPathSuffix that is empty string, clears VolumeID and LocalBasePath + */ + public CNRLink createCommonNetworkRelativeLink() { + cnrlink = new CNRLink(); + commonPathSuffix = ""; + lif.setCommonNetworkRelativeLinkAndPathSuffix(); + + vid = null; + localBasePath = null; + lif.clearVolumeIDAndLocalBasePath(); + + return cnrlink; + } + + public String getCommonPathSuffix() { return commonPathSuffix; } + /** + * Set CommonPathSuffix and creates new CommonNetworkRelativeLink (if it not exists), clears VolumeID and LocalBasePath. + * If s is null takes no effect + */ + public void setCommonPathSuffix(String s) { + if (s == null) return; + + localBasePath = null; + vid = null; + lif.clearVolumeIDAndLocalBasePath(); + + commonPathSuffix = s; + if (cnrlink == null) cnrlink = new CNRLink(); + lif.setCommonNetworkRelativeLinkAndPathSuffix(); + } +} diff --git a/src/mslinks/LinkTargetIDList.java b/src/mslinks/LinkTargetIDList.java index 4bd9f48f5db90743f7326b8e3c78415734ba98b1..4dcd2ac8ff1c901f9614048a3b70277c6de4fdde 100644 --- a/src/mslinks/LinkTargetIDList.java +++ b/src/mslinks/LinkTargetIDList.java @@ -6,13 +6,13 @@ import io.ByteWriter; import java.io.IOException; import java.util.LinkedList; -public class LinkTargetIDList { +public class LinkTargetIDList implements Serializable { private LinkedList<byte[]> list = new LinkedList<>(); public LinkTargetIDList(ByteReader data) throws IOException, ShellLinkException { int size = (int)data.read2bytes(); - int check = data.getPosition(); + int pos = data.getPosition(); int s = (int)data.read2bytes(); while (s != 0) { @@ -24,8 +24,8 @@ public class LinkTargetIDList { s = (int)data.read2bytes(); } - check = data.getPosition() - check; - if (check != size) + pos = data.getPosition() - pos; + if (pos != size) throw new ShellLinkException(); } diff --git a/src/mslinks/Main.java b/src/mslinks/Main.java index 4395b58a4223984cf92aeefcc84450414e9fc335..1fed2fda23152a936da05fda4d5ad55e654e3cc1 100644 --- a/src/mslinks/Main.java +++ b/src/mslinks/Main.java @@ -9,8 +9,12 @@ import mslinks.data.Filetime; public class Main { public static void main(String[] args) throws IOException, ShellLinkException { - ShellLink link = new ShellLink("testlink.lnk"); - Filetime ft = link.getWriteTime(); + //for (String i : Charset.availableCharsets().keySet()) + // System.out.println(i); + //if (true) return; + + ShellLink link = new ShellLink("testlink3.lnk"); + Filetime ft = link.getHeader().getWriteTime(); System.out.println(String.format("%d:%d:%d %d.%d.%d", ft.get(GregorianCalendar.HOUR_OF_DAY), ft.get(GregorianCalendar.MINUTE), ft.get(GregorianCalendar.SECOND), ft.get(GregorianCalendar.DAY_OF_MONTH), ft.get(GregorianCalendar.MONTH) + 1, ft.get(GregorianCalendar.YEAR))); diff --git a/src/mslinks/Serializable.java b/src/mslinks/Serializable.java new file mode 100644 index 0000000000000000000000000000000000000000..ec68dd58561d099df8f56884a0ff4da6d7fe10c9 --- /dev/null +++ b/src/mslinks/Serializable.java @@ -0,0 +1,9 @@ +package mslinks; + +import java.io.IOException; + +import io.ByteWriter; + +public interface Serializable { + void serialize(ByteWriter bw) throws IOException; +} diff --git a/src/mslinks/ShellLink.java b/src/mslinks/ShellLink.java index d720fe49cc3ff95e134e6f0355a39ae27ee95911..87e71ce4b0ce4de86cb6361e2fe8a1a7e3e77a07 100644 --- a/src/mslinks/ShellLink.java +++ b/src/mslinks/ShellLink.java @@ -8,16 +8,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import mslinks.data.FileAttributesFlags; -import mslinks.data.Filetime; -import mslinks.data.HotKeyFlags; -import mslinks.data.LinkFlags; - public class ShellLink { private boolean le; private ShellLinkHeader header; private LinkTargetIDList idlist; + private LinkInfo info; public ShellLink(String file) throws IOException, ShellLinkException { this(Paths.get(file)); @@ -39,6 +35,8 @@ public class ShellLink { header = new ShellLinkHeader(data); if (header.getLinkFlags().hasLinkTargetIDList()) idlist = new LinkTargetIDList(data); + if (header.getLinkFlags().hasLinkInfo()) + info = new LinkInfo(data); le = data.isLitteEndian(); } @@ -47,23 +45,17 @@ public class ShellLink { if (le) bw.setLittleEndian(); else bw.setBigEndian(); header.serialize(bw); - idlist.serialize(bw); + if (header.getLinkFlags().hasLinkTargetIDList()) + idlist.serialize(bw); + if (header.getLinkFlags().hasLinkInfo()) + info.serialize(bw); } - /* to header */ - public LinkFlags getLinkFlags() { return header.getLinkFlags(); } - public FileAttributesFlags getFileAttributesFlags() { return header.getFileAttributesFlags(); } - public Filetime getCreationTime() { return header.getCreationTime(); } - public Filetime getAccessTime() { return header.getAccessTime(); } - public Filetime getWriteTime() { return header.getWriteTime(); } - public HotKeyFlags getHotKeyFlags() { return header.getHotKeyFlags(); } - - public int getFileSize() { return header.getFileSize(); } - public void setFileSize(long n) { header.setFileSize(n); } + public ShellLinkHeader getHeader() { return header; } - public int getIconIndex() { return header.getIconIndex(); } - public void setIconIndex(int n) { header.setIconIndex(n); } - - public int getShowCommand() { return header.getShowCommand(); } - public void setShowCommand(int n) throws ShellLinkException { header.setShowCommand(n); } + public LinkInfo getLinkInfo() { return info; } + public void createLinkInfo() { + info = new LinkInfo(); + header.getLinkFlags().setHasLinkInfo(); + } } diff --git a/src/mslinks/ShellLinkHeader.java b/src/mslinks/ShellLinkHeader.java index 673df7f6edf9898260fedf817d54af0815b0e4f4..2655ad38c92b4588313c5f73f9eaecb45fa857ff 100644 --- a/src/mslinks/ShellLinkHeader.java +++ b/src/mslinks/ShellLinkHeader.java @@ -12,7 +12,7 @@ import mslinks.data.GUID; import mslinks.data.HotKeyFlags; import mslinks.data.LinkFlags; -public class ShellLinkHeader { +public class ShellLinkHeader implements Serializable { private static byte b(int i) { return (byte)i; } private static int headerSize = 0x0000004C; private static GUID clsid = new GUID(new byte[] { @@ -22,9 +22,9 @@ public class ShellLinkHeader { b(0xc0), b(0x00), b(0x00), b(0x00), b(0x00), b(0x00), b(0x00), b(0x46) }); - public static int SW_SHOWNORMAL = 1; - public static int SW_SHOWMAXIMIZED = 3; - public static int SW_SHOWMINNOACTIVE = 7; + public static final int SW_SHOWNORMAL = 1; + public static final int SW_SHOWMAXIMIZED = 3; + public static final int SW_SHOWMINNOACTIVE = 7; private LinkFlags lf; private FileAttributesFlags faf; @@ -54,6 +54,8 @@ public class ShellLinkHeader { fileSize = (int)data.read4bytes(); iconIndex = (int)data.read4bytes(); showCommand = (int)data.read4bytes(); + if (showCommand != SW_SHOWNORMAL && showCommand != SW_SHOWMAXIMIZED && showCommand != SW_SHOWMINNOACTIVE) + throw new ShellLinkException(); hkf = new HotKeyFlags(data); data.read2bytes(); data.read8bytes(); diff --git a/src/mslinks/data/BitSet32.java b/src/mslinks/data/BitSet32.java index ae7bbc919eb2704565bbbdfaccb6efd6c35d6fae..89ae3b22e7829d7797a439202c28cc7aa03c4dbf 100644 --- a/src/mslinks/data/BitSet32.java +++ b/src/mslinks/data/BitSet32.java @@ -5,7 +5,9 @@ import io.ByteWriter; import java.io.IOException; -public class BitSet32 { +import mslinks.Serializable; + +public class BitSet32 implements Serializable { private int d; public BitSet32(int n) { diff --git a/src/mslinks/data/CNRLink.java b/src/mslinks/data/CNRLink.java new file mode 100644 index 0000000000000000000000000000000000000000..546e99195ec2b678b593b4e02c97c4559f1bcd14 --- /dev/null +++ b/src/mslinks/data/CNRLink.java @@ -0,0 +1,201 @@ +package mslinks.data; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; + +import mslinks.Serializable; +import mslinks.ShellLinkException; +import io.ByteReader; +import io.ByteWriter; + +public class CNRLink implements Serializable { + + public static final int WNNC_NET_AVID = 0x001A000; + public static final int WNNC_NET_DOCUSPACE = 0x001B000; + public static final int WNNC_NET_MANGOSOFT = 0x001C000; + public static final int WNNC_NET_SERNET = 0x001D000; + public static final int WNNC_NET_RIVERFRONT1 = 0X001E000; + public static final int WNNC_NET_RIVERFRONT2 = 0x001F000; + public static final int WNNC_NET_DECORB = 0x0020000; + public static final int WNNC_NET_PROTSTOR = 0x0021000; + public static final int WNNC_NET_FJ_REDIR = 0x0022000; + public static final int WNNC_NET_DISTINCT = 0x0023000; + public static final int WNNC_NET_TWINS = 0x0024000; + public static final int WNNC_NET_RDR2SAMPLE = 0x0025000; + public static final int WNNC_NET_CSC = 0x0026000; + public static final int WNNC_NET_3IN1 = 0x0027000; + public static final int WNNC_NET_EXTENDNET = 0x0029000; + public static final int WNNC_NET_STAC = 0x002A000; + public static final int WNNC_NET_FOXBAT = 0x002B000; + public static final int WNNC_NET_YAHOO = 0x002C000; + public static final int WNNC_NET_EXIFS = 0x002D000; + public static final int WNNC_NET_DAV = 0x002E000; + public static final int WNNC_NET_KNOWARE = 0x002F000; + public static final int WNNC_NET_OBJECT_DIRE = 0x0030000; + public static final int WNNC_NET_MASFAX = 0x0031000; + public static final int WNNC_NET_HOB_NFS = 0x0032000; + public static final int WNNC_NET_SHIVA = 0x0033000; + public static final int WNNC_NET_IBMAL = 0x0034000; + public static final int WNNC_NET_LOCK = 0x0035000; + public static final int WNNC_NET_TERMSRV = 0x0036000; + public static final int WNNC_NET_SRT = 0x0037000; + public static final int WNNC_NET_QUINCY = 0x0038000; + public static final int WNNC_NET_OPENAFS = 0x0039000; + public static final int WNNC_NET_AVID1 = 0X003A000; + public static final int WNNC_NET_DFS = 0x003B000; + public static final int WNNC_NET_KWNP = 0x003C000; + public static final int WNNC_NET_ZENWORKS = 0x003D000; + public static final int WNNC_NET_DRIVEONWEB = 0x003E000; + public static final int WNNC_NET_VMWARE = 0x003F000; + public static final int WNNC_NET_RSFX = 0x0040000; + public static final int WNNC_NET_MFILES = 0x0041000; + public static final int WNNC_NET_MS_NFS = 0x0042000; + public static final int WNNC_NET_GOOGLE = 0x0043000; + + private CNRLinkFlags flags; + private int nptype; + private String netname, devname; + + public CNRLink() { + netname = ""; + } + + public CNRLink(ByteReader data) throws ShellLinkException, IOException { + int pos = data.getPosition(); + int size = (int)data.read4bytes(); + if (size < 0x14) + throw new ShellLinkException(); + flags = new CNRLinkFlags(data); + int nnoffset = (int)data.read4bytes(); + int dnoffset = (int)data.read4bytes(); + if (!flags.isValidDevice()) + dnoffset = 0; + nptype = (int)data.read4bytes(); + if (flags.isValidNetType()) + checkNptype(nptype); + else nptype = 0; + + int nnoffset_u = 0, dnoffset_u = 0; + if (nnoffset > 0x14) { + nnoffset_u = (int)data.read4bytes(); + dnoffset_u = (int)data.read4bytes(); + } + + data.seek(pos + nnoffset - data.getPosition()); + netname = data.readString( pos, size); + if (dnoffset != 0) { + data.seek(pos + dnoffset - data.getPosition()); + devname = data.readString(pos, size); + } + if (nnoffset_u != 0) netname = data.readUnicodeString(pos, size); + if (dnoffset_u != 0) devname = data.readUnicodeString(pos, size); + } + + private void checkNptype(int type) throws ShellLinkException { + int mod = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL; + for (Field f : this.getClass().getFields()) { + try { + if ((f.getModifiers() & mod) == mod && type == ((Integer)f.get(null)).intValue()) + return; + } catch (Exception e) {} + } + throw new ShellLinkException("incorrect network type"); + } + + @Override + public void serialize(ByteWriter bw) throws IOException { + int size = 20; + boolean u = false; + CharsetEncoder ce = Charset.defaultCharset().newEncoder(); + u = !ce.canEncode(netname) || devname != null && !ce.canEncode(devname); + + if (u) size += 8; + byte[] netname_b = null, devname_b = null; + netname_b = netname.getBytes(); + if (devname != null) devname_b = devname.getBytes(); + size += netname_b.length + 1; + if (devname_b != null) size += devname_b.length + 1; + + if (u) { + size += netname.length() * 2 + 2; + if (devname != null) size += devname.length() * 2 + 2; + } + + bw.write4bytes(size); + flags.serialize(bw); + int off = 20; + if (u) off += 8; + bw.write4bytes(off); // netname offset + off += netname_b.length + 1; + if (devname_b != null) { + bw.write4bytes(off); // devname offset + off += devname_b.length + 1; + } else bw.write4bytes(0); + bw.write4bytes(nptype); + if (u) { + bw.write4bytes(off); + off += netname.length() * 2 + 2; + if (devname != null) { + bw.write4bytes(off); + off += devname.length() * 2 + 2; + } else bw.write4bytes(0); + } + bw.writeBytes(netname_b); + bw.write(0); + if (devname_b != null) { + bw.writeBytes(devname_b); + bw.write(0); + } + if (u) { + for (int i=0; i<netname.length(); i++) + bw.write2bytes(netname.charAt(i)); + bw.write2bytes(0); + if (devname != null) { + for (int i=0; i<devname.length(); i++) + bw.write2bytes(devname.charAt(i)); + bw.write2bytes(0); + } + } + } + + public int getNetworkType() { return nptype; } + /** + * pass zero to switch off network type + */ + public void setNetworlType(int n) throws ShellLinkException { + if (n == 0) { + flags.clearValidNetType(); + nptype = n; + } else { + checkNptype(n); + flags.setValidNetType(); + nptype = n; + } + } + + public String getNetName() { return netname; } + /** + * if s is null take no effect + */ + public void setNetName(String s) throws ShellLinkException { + if (s == null) return; + netname = s; + } + + public String getDeviceName() { return devname; } + /** + * pass null to switch off device info + */ + public void setDeviceName(String s) { + if (s == null) { + devname = null; + flags.clearValidDevice(); + } else { + devname = s; + flags.setValidDevice(); + } + } +} diff --git a/src/mslinks/data/CNRLinkFlags.java b/src/mslinks/data/CNRLinkFlags.java new file mode 100644 index 0000000000000000000000000000000000000000..69438d29426cbd9bfd278d79e0f0aee5a9d0754d --- /dev/null +++ b/src/mslinks/data/CNRLinkFlags.java @@ -0,0 +1,33 @@ +package mslinks.data; + +import io.ByteReader; + +import java.io.IOException; + +public class CNRLinkFlags extends BitSet32 { + + public CNRLinkFlags(int n) { + super(n); + reset(); + } + + public CNRLinkFlags(ByteReader data) throws IOException { + super(data); + reset(); + } + + private void reset() { + for (int i=2; i<32; i++) + clear(i); + } + + public boolean isValidDevice() { return get(0); } + public boolean isValidNetType() { return get(1); } + + public void setValidDevice() { set(0); } + public void setValidNetType() { set(1); } + + public void clearValidDevice() { clear(0); } + public void clearValidNetType() { clear(1); } + +} diff --git a/src/mslinks/data/FileAttributesFlags.java b/src/mslinks/data/FileAttributesFlags.java index d8fb16660392a19533537180b2a5cb08d3b186b5..533c13966105e113f508cd7b26e7e909b83d6762 100644 --- a/src/mslinks/data/FileAttributesFlags.java +++ b/src/mslinks/data/FileAttributesFlags.java @@ -7,10 +7,19 @@ import java.io.IOException; public class FileAttributesFlags extends BitSet32 { public FileAttributesFlags(int n) { super(n); + reset(); } public FileAttributesFlags(ByteReader data) throws IOException { super(data); + reset(); + } + + private void reset() { + clear(3); + clear(6); + for (int i=15; i<32; i++) + clear(i); } public boolean isReadonly() { return get(0); } diff --git a/src/mslinks/data/Filetime.java b/src/mslinks/data/Filetime.java index 3fc824f3291cd22ff145befd800feb865214e191..2d216363ddaf908ee86fbea0875befac097580da 100644 --- a/src/mslinks/data/Filetime.java +++ b/src/mslinks/data/Filetime.java @@ -6,7 +6,9 @@ import io.ByteWriter; import java.io.IOException; import java.util.GregorianCalendar; -public class Filetime extends GregorianCalendar { +import mslinks.Serializable; + +public class Filetime extends GregorianCalendar implements Serializable { private long residue; public Filetime(ByteReader data) throws IOException { diff --git a/src/mslinks/data/GUID.java b/src/mslinks/data/GUID.java index 4c820a0f8e043dac42df0c49bf6f93fabd4c0dc4..c3e65a30e04352c232a393445401670d8615f195 100644 --- a/src/mslinks/data/GUID.java +++ b/src/mslinks/data/GUID.java @@ -6,7 +6,9 @@ import io.Bytes; import java.io.IOException; -public class GUID { +import mslinks.Serializable; + +public class GUID implements Serializable { private int d1; private short d2, d3, d4; private long d5; diff --git a/src/mslinks/data/HotKeyFlags.java b/src/mslinks/data/HotKeyFlags.java index 6c780aac3d59cd6791866141b9d023f9e5b9b2d8..c919094222793c704c07a68c733c02dd9909ceba 100644 --- a/src/mslinks/data/HotKeyFlags.java +++ b/src/mslinks/data/HotKeyFlags.java @@ -6,7 +6,9 @@ import io.ByteWriter; import java.io.IOException; import java.util.HashMap; -public class HotKeyFlags { +import mslinks.Serializable; + +public class HotKeyFlags implements Serializable { private static HashMap<Byte, String> keys = new HashMap<Byte, String>() {{ put((byte)0x30, "0"); put((byte)0x31, "1"); diff --git a/src/mslinks/data/LinkFlags.java b/src/mslinks/data/LinkFlags.java index c565cbd4796945249c95d625888f0a45007faffd..fd65096d54b2e1d455e88ec8dfa669c7a66650ef 100644 --- a/src/mslinks/data/LinkFlags.java +++ b/src/mslinks/data/LinkFlags.java @@ -8,10 +8,19 @@ public class LinkFlags extends BitSet32 { public LinkFlags(int n) { super(n); + reset(); } public LinkFlags(ByteReader data) throws IOException { super(data); + reset(); + } + + private void reset() { + clear(11); + clear(16); + for (int i=27; i<32; i++) + clear(i); } public boolean hasLinkTargetIDList() { return get(0); } diff --git a/src/mslinks/data/LinkInfoFlags.java b/src/mslinks/data/LinkInfoFlags.java new file mode 100644 index 0000000000000000000000000000000000000000..3514c68819db760e5dca405ed159ca9925db0159 --- /dev/null +++ b/src/mslinks/data/LinkInfoFlags.java @@ -0,0 +1,32 @@ +package mslinks.data; + +import io.ByteReader; + +import java.io.IOException; + +public class LinkInfoFlags extends BitSet32 { + + public LinkInfoFlags(int n) { + super(n); + reset(); + } + + public LinkInfoFlags(ByteReader data) throws IOException { + super(data); + reset(); + } + + private void reset() { + for (int i=2; i<32; i++) + clear(i); + } + + public boolean hasVolumeIDAndLocalBasePath() { return get(0); } + public boolean hasCommonNetworkRelativeLinkAndPathSuffix() { return get(1); } + + public void setVolumeIDAndLocalBasePath() { set(0); } + public void setCommonNetworkRelativeLinkAndPathSuffix() { set(1); } + + public void clearVolumeIDAndLocalBasePath() { clear(0); } + public void clearCommonNetworkRelativeLinkAndPathSuffix() { clear(1); } +} diff --git a/src/mslinks/data/VolumeID.java b/src/mslinks/data/VolumeID.java new file mode 100644 index 0000000000000000000000000000000000000000..f186d78024f5a2d2f914601aebe15900ad501d19 --- /dev/null +++ b/src/mslinks/data/VolumeID.java @@ -0,0 +1,125 @@ +package mslinks.data; + +import java.io.IOException; +import java.nio.charset.Charset; + +import mslinks.Serializable; +import mslinks.ShellLinkException; +import io.ByteReader; +import io.ByteWriter; + +public class VolumeID implements Serializable { + public static final int DRIVE_UNKNOWN = 0; + public static final int DRIVE_NO_ROOT_DIR = 1; + public static final int DRIVE_REMOVABLE = 2; + public static final int DRIVE_FIXED = 3; + public static final int DRIVE_REMOTE = 4; + public static final int DRIVE_CDROM = 5; + public static final int DRIVE_RAMDISK = 6; + + + private int dt; + private int dsn; + private String label; + + public VolumeID() { + dt = DRIVE_UNKNOWN; + dsn = (int)(Math.random() * Long.MAX_VALUE); + label = ""; + } + + public VolumeID(ByteReader data) throws ShellLinkException, IOException { + int pos = data.getPosition(); + int size = (int)data.read4bytes(); + if (size <= 0x10) + throw new ShellLinkException(); + + dt = (int)data.read4bytes(); + if (dt != DRIVE_NO_ROOT_DIR && dt != DRIVE_REMOVABLE && dt != DRIVE_FIXED + && dt != DRIVE_REMOTE && dt != DRIVE_CDROM && dt != DRIVE_RAMDISK) + dt = DRIVE_UNKNOWN; + dsn = (int)data.read4bytes(); + int vloffset = (int)data.read4bytes(); + boolean u = false; + if (vloffset == 0x14) { + vloffset = (int)data.read4bytes(); + u = true; + } + + data.seek(pos + vloffset - data.getPosition()); + + int i=0; + if (u) { + char[] buf = new char[(size-vloffset)>>1]; + for (;; i++) { + char c = (char)data.read2bytes(); + if (c == 0) break; + buf[i] = c; + } + label = new String(buf, 0, i); + } else { + byte[] buf = new byte[size-vloffset]; + for (;; i++) { + int b = data.read(); + if (b == 0) break; + buf[i] = (byte)b; + } + label = new String(buf, 0, i); + } + } + + public void serialize(ByteWriter bw) throws IOException { + int size = 16; + byte[] label_b = label.getBytes(); + size += label_b.length + 1; + boolean u = false; + if (!Charset.defaultCharset().newEncoder().canEncode(label)) { + size += 4 + 1 + label.length() * 2 + 2; + u = true; + } + + bw.write4bytes(size); + bw.write4bytes(dt); + bw.write4bytes(dsn); + int off = 16; + if (u) off += 4; + bw.write4bytes(off); + off += label_b.length + 1; + if (u) { + off++; + bw.write4bytes(off); + off += label.length() * 2 + 2; + } + + bw.writeBytes(label_b); + bw.write(0); + if (u) { + bw.write(0); + for (int i=0; i<label.length(); i++) + bw.write2bytes(label.charAt(i)); + bw.write2bytes(0); + } + } + + public int getDriveType() { return dt;} + public void setDriveType(int n) throws ShellLinkException { + if (n == DRIVE_UNKNOWN || n == DRIVE_NO_ROOT_DIR || n == DRIVE_REMOVABLE || n == DRIVE_FIXED + || n == DRIVE_REMOTE || n == DRIVE_CDROM || n == DRIVE_RAMDISK) + dt = n; + else + throw new ShellLinkException("incorrect drive type"); + } + + public int getSerialNumber() { return dsn; } + public void setSerialNumber(int n) { dsn = n; } + + public String getLabel() { return label; } + /** + * if s is null take no effect + */ + public void setLabel(String s) { + if (s == null) return; + label = s; + } + +} diff --git a/test.lnk b/test.lnk index 7dea24b522b7019a5816e696f1463e6a2c8821ab..d8688532def8dbb7b23430c8e5233522249c54fa 100644 Binary files a/test.lnk and b/test.lnk differ diff --git a/testlink.lnk b/testlink.lnk index 869c5c67a2db6a48a8d98ab8bb9fa422890fed37..edce87d281ba6ff62ee8225626bf5fdfe21a1661 100644 Binary files a/testlink.lnk and b/testlink.lnk differ diff --git a/testlink2.lnk b/testlink2.lnk new file mode 100644 index 0000000000000000000000000000000000000000..a28907e3c32664b53979dc7f2d65c3c925abf501 Binary files /dev/null and b/testlink2.lnk differ diff --git a/testlink3.lnk b/testlink3.lnk new file mode 100644 index 0000000000000000000000000000000000000000..35f78d76f470af829aec901d3b277c2ef0247e07 Binary files /dev/null and b/testlink3.lnk differ diff --git a/testlink4.lnk b/testlink4.lnk new file mode 100644 index 0000000000000000000000000000000000000000..84f15f7bc5b3c09d830897392de8088e4d84aa20 Binary files /dev/null and b/testlink4.lnk differ diff --git a/testlink5.lnk b/testlink5.lnk new file mode 100644 index 0000000000000000000000000000000000000000..d4866223362b1c9741c2b2a023d7482dc81cf4dd Binary files /dev/null and b/testlink5.lnk differ diff --git a/testlink6.lnk b/testlink6.lnk new file mode 100644 index 0000000000000000000000000000000000000000..346797d658169a1fec2e5600cf9e800c07def958 Binary files /dev/null and b/testlink6.lnk differ