All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twelvemonkeys.io.Win32Lnk Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name "TwelveMonkeys" nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.twelvemonkeys.io;

import java.io.*;
import java.util.Arrays;

/**
 * A {@code File} implementation that resolves the Windows {@code .lnk} files as symbolic links.
 * 

* This class is based on example code from * Swing Hacks, * By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30. * * @author Harald Kuhr * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32Lnk.java#2 $ */ final class Win32Lnk extends File { private final static byte[] LNK_MAGIC = { 'L', 0x00, 0x00, 0x00, // Magic }; private final static byte[] LNK_GUID = { 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Shell Link GUID (byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F' }; private final File mTarget; private static final int FLAG_ITEM_ID_LIST = 0x01; private static final int FLAG_FILE_LOC_INFO = 0x02; private static final int FLAG_DESC_STRING = 0x04; private static final int FLAG_REL_PATH_STRING = 0x08; private static final int FLAG_WORKING_DIRECTORY = 0x10; private static final int FLAG_COMMAND_LINE_ARGS = 0x20; private static final int FLAG_ICON_FILENAME = 0x40; private static final int FLAG_ADDITIONAL_INFO = 0x80; private Win32Lnk(final String pPath) throws IOException { super(pPath); File target = parse(this); if (target == this) { // NOTE: This is a workaround // mTarget = this causes infinite loops in some methods target = new File(pPath); } mTarget = target; } Win32Lnk(final File pPath) throws IOException { this(pPath.getPath()); } /** * Parses a {@code .lnk} file to find the real file. * * @param pPath the path to the {@code .lnk} file * @return a new file object that * @throws java.io.IOException if the {@code .lnk} cannot be parsed */ static File parse(final File pPath) throws IOException { if (!pPath.getName().endsWith(".lnk")) { return pPath; } File result = pPath; LittleEndianDataInputStream in = new LittleEndianDataInputStream(new BufferedInputStream(new FileInputStream(pPath))); try { byte[] magic = new byte[4]; in.readFully(magic); byte[] guid = new byte[16]; in.readFully(guid); if (!(Arrays.equals(LNK_MAGIC, magic) && Arrays.equals(LNK_GUID, guid))) { //System.out.println("Not a symlink"); // Not a symlink return pPath; } // Get the flags int flags = in.readInt(); //System.out.println("flags: " + Integer.toBinaryString(flags & 0xff)); // Get to the file settings /*int attributes = */in.readInt(); // File attributes // 0 Target is read only. // 1 Target is hidden. // 2 Target is a system file. // 3 Target is a volume label. (Not possible) // 4 Target is a directory. // 5 Target has been modified since last backup. (archive) // 6 Target is encrypted (NTFS EFS) // 7 Target is Normal?? // 8 Target is temporary. // 9 Target is a sparse file. // 10 Target has reparse point data. // 11 Target is compressed. // 12 Target is offline. //System.out.println("attributes: " + Integer.toBinaryString(attributes)); // NOTE: Cygwin .lnks are not directory links, can't rely on this.. :-/ in.skipBytes(48); // TODO: Make sense of this data... // Skipped data: // long time 1 (creation) // long time 2 (modification) // long time 3 (last access) // int file length // int icon number // int ShowVnd value // int hotkey // int, int - unknown: 0,0 // If the shell settings are present, skip them if ((flags & FLAG_ITEM_ID_LIST) != 0) { // Shell Item Id List present //System.out.println("Shell Item Id List present"); int shellLen = in.readShort(); // Short //System.out.println("shellLen: " + shellLen); // TODO: Probably need to parse this data, to determine // Cygwin folders... /* int read = 2; int itemLen = in.readShort(); while (itemLen > 0) { System.out.println("--> ITEM: " + itemLen); BufferedReader reader = new BufferedReader(new InputStreamReader(new SubStream(in, itemLen - 2))); //byte[] itemBytes = new byte[itemLen - 2]; // NOTE: Lenght included //in.readFully(itemBytes); String item = reader.readLine(); System.out.println("item: \"" + item + "\""); itemLen = in.readShort(); read += itemLen; } System.out.println("read: " + read); */ in.skipBytes(shellLen); } if ((flags & FLAG_FILE_LOC_INFO) != 0) { // File Location Info Table present //System.out.println("File Location Info Table present"); // 0h 1 dword This is the total length of this structure and all following data // 4h 1 dword This is a pointer to first offset after this structure. 1Ch // 8h 1 dword Flags // Ch 1 dword Offset of local volume info // 10h 1 dword Offset of base pathname on local system // 14h 1 dword Offset of network volume info // 18h 1 dword Offset of remaining pathname // Flags: // Bit Meaning // 0 Available on a local volume // 1 Available on a network share // TODO: Make sure the path is on a local disk, etc.. int tableLen = in.readInt(); // Int //System.out.println("tableLen: " + tableLen); in.readInt(); // Skip int locFlags = in.readInt(); //System.out.println("locFlags: " + Integer.toBinaryString(locFlags)); if ((locFlags & 0x01) != 0) { //System.out.println("Available local"); } if ((locFlags & 0x02) != 0) { //System.err.println("Available on network path"); } // Get the local volume and local system values in.skipBytes(4); // TODO: see above for structure int localSysOff = in.readInt(); //System.out.println("localSysOff: " + localSysOff); in.skipBytes(localSysOff - 20); // Relative to start of chunk byte[] pathBytes = new byte[tableLen - localSysOff - 1]; in.readFully(pathBytes, 0, pathBytes.length); String path = new String(pathBytes, 0, pathBytes.length - 1); /* ByteArrayOutputStream bytes = new ByteArrayOutputStream(); byte read; // Read bytes until the null (0) character while (true) { read = in.readByte(); if (read == 0) { break; } bytes.write(read & 0xff); } String path = new String(bytes.toByteArray(), 0, bytes.size()); //*/ // Recurse to end of link chain // TODO: This may cause endless loop if cyclic chain... //System.out.println("path: \"" + path + "\""); try { result = parse(new File(path)); } catch (StackOverflowError e) { throw new IOException("Cannot resolve cyclic link: " + e.getMessage()); } } if ((flags & FLAG_DESC_STRING) != 0) { // Description String present, skip it. //System.out.println("Description String present"); // The string length is the first word which must also be skipped. int descLen = in.readShort(); //System.out.println("descLen: " + descLen); byte[] descBytes = new byte[descLen]; in.readFully(descBytes, 0, descLen); //String desc = new String(descBytes, 0, descLen); //System.out.println("desc: " + desc); } if ((flags & FLAG_REL_PATH_STRING) != 0) { // Relative Path String present //System.out.println("Relative Path String present"); // The string length is the first word which must also be skipped. int pathLen = in.readShort(); //System.out.println("pathLen: " + pathLen); byte[] pathBytes = new byte[pathLen]; in.readFully(pathBytes, 0, pathLen); String path = new String(pathBytes, 0, pathLen); // TODO: This may cause endless loop if cyclic chain... //System.out.println("path: \"" + path + "\""); if (result == pPath) { try { result = parse(new File(pPath.getParentFile(), path)); } catch (StackOverflowError e) { throw new IOException("Cannot resolve cyclic link: " + e.getMessage()); } } } if ((flags & FLAG_WORKING_DIRECTORY) != 0) { //System.out.println("Working Directory present"); } if ((flags & FLAG_COMMAND_LINE_ARGS) != 0) { //System.out.println("Command Line Arguments present"); // NOTE: This means this .lnk is not a folder, don't follow result = pPath; } if ((flags & FLAG_ICON_FILENAME) != 0) { //System.out.println("Icon Filename present"); } if ((flags & FLAG_ADDITIONAL_INFO) != 0) { //System.out.println("Additional Info present"); } } finally { in.close(); } return result; } /* private static String getNullDelimitedString(byte[] bytes, int off) { int len = 0; // Count bytes until the null (0) character while (true) { if (bytes[off + len] == 0) { break; } len++; } System.err.println("--> " + len); return new String(bytes, off, len); } */ /** * Converts two bytes into a short. *

* NOTE: this is little endian because it's for an * Intel only OS * * @ param bytes * @ param off * @return the bytes as a short. */ /* private static int bytes2short(byte[] bytes, int off) { return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); } */ public File getTarget() { return mTarget; } // java.io.File overrides below @Override public boolean isDirectory() { return mTarget.isDirectory(); } @Override public boolean canRead() { return mTarget.canRead(); } @Override public boolean canWrite() { return mTarget.canWrite(); } // NOTE: equals is implemented using compareto == 0 /* public int compareTo(File pathname) { // TODO: Verify this // Probably not a good idea, as it IS NOT THE SAME file // It's probably better to not override return mTarget.compareTo(pathname); } */ // Should probably never allow creating a new .lnk // public boolean createNewFile() throws IOException // Deletes only the .lnk // public boolean delete() { //public void deleteOnExit() { @Override public boolean exists() { return mTarget.exists(); } // A .lnk may be absolute //public File getAbsoluteFile() { //public String getAbsolutePath() { // Theses should be resolved according to the API (for Unix). @Override public File getCanonicalFile() throws IOException { return mTarget.getCanonicalFile(); } @Override public String getCanonicalPath() throws IOException { return mTarget.getCanonicalPath(); } //public String getName() { // I guess the parent should be the parent of the .lnk, not the target //public String getParent() { //public File getParentFile() { // public boolean isAbsolute() { @Override public boolean isFile() { return mTarget.isFile(); } @Override public boolean isHidden() { return mTarget.isHidden(); } @Override public long lastModified() { return mTarget.lastModified(); } @Override public long length() { return mTarget.length(); } @Override public String[] list() { return mTarget.list(); } @Override public String[] list(final FilenameFilter filter) { return mTarget.list(filter); } @Override public File[] listFiles() { return Win32File.wrap(mTarget.listFiles()); } @Override public File[] listFiles(final FileFilter filter) { return Win32File.wrap(mTarget.listFiles(filter)); } @Override public File[] listFiles(final FilenameFilter filter) { return Win32File.wrap(mTarget.listFiles(filter)); } // Makes no sense, does it? //public boolean mkdir() { //public boolean mkdirs() { // Only rename the lnk //public boolean renameTo(File dest) { @Override public boolean setLastModified(long time) { return mTarget.setLastModified(time); } @Override public boolean setReadOnly() { return mTarget.setReadOnly(); } @Override public String toString() { if (mTarget.equals(this)) { return super.toString(); } return super.toString() + " -> " + mTarget.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy