From 2df0e73f2d10339cd837b0aa3f216ff140cd53e7 Mon Sep 17 00:00:00 2001 From: Jim Procter Date: Thu, 24 May 2018 11:56:32 +0100 Subject: [PATCH 1/1] =?utf8?q?JAL-1460=20process=20.lnk=20files=20dragged=20?= =?utf8?q?to=20Jalview=20when=20on=20Windows=20using=20WindowsShortCutHandle?= =?utf8?q?r=20class=20(thanks=20to=20StackOverFlow=20post..)=E2=80=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- THIRDPARTYLIBS | 1 + src/jalview/gui/Desktop.java | 25 +++ .../stackoverflowusers/file/WindowsShortcut.java | 215 ++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 src/org/stackoverflowusers/file/WindowsShortcut.java diff --git a/THIRDPARTYLIBS b/THIRDPARTYLIBS index e2baa85..d0d9125 100644 --- a/THIRDPARTYLIBS +++ b/THIRDPARTYLIBS @@ -9,6 +9,7 @@ ext.edu.ucsf.rbvi.strucviz2 includes sources originally developed by Scooter Mor jalview.ext.android includes code taken from the Android Open Source Project (https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util). The Apache 2.0 Licence (http://www.apache.org/licenses/LICENSE-2.0) is acknowledged in the source code. + org.stackoverflowusers.file.WindowsShortcuts was downloaded from http://github.com/codebling/WindowsShortcuts via https://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java Licensing information for each library is given below: diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 9598f60..e14e991 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -32,6 +32,7 @@ import jalview.io.FileFormatException; import jalview.io.FileFormatI; import jalview.io.FileFormats; import jalview.io.FileLoader; +import jalview.io.FormatAdapter; import jalview.io.IdentifyFile; import jalview.io.JalviewFileChooser; import jalview.io.JalviewFileView; @@ -116,6 +117,8 @@ import javax.swing.event.InternalFrameEvent; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; +import org.stackoverflowusers.file.WindowsShortcut; + /** * Jalview Desktop * @@ -3404,6 +3407,28 @@ public class Desktop extends jalview.jbgui.GDesktop files.add(file.toString()); } } + if (Platform.isWindows()) + { + Cache.log.debug("Scanning dropped content for Windows Link Files"); + + // resolve any .lnk files in the file drop + for (int f=0;f= minimum_length + && isMagicPresent(getBytes(fis, 32)); + } finally { + fis.close(); + } + return isPotentiallyValid; + } + + public WindowsShortcut(File file) throws IOException, ParseException { + InputStream in = new FileInputStream(file); + try { + parseLink(getBytes(in)); + } finally { + in.close(); + } + } + + /** + * @return the name of the filesystem object pointed to by this shortcut + */ + public String getRealFilename() { + return real_file; + } + + /** + * Tests if the shortcut points to a local resource. + * @return true if the 'local' bit is set in this shortcut, false otherwise + */ + public boolean isLocal() { + return isLocal; + } + + /** + * Tests if the shortcut points to a directory. + * @return true if the 'directory' bit is set in this shortcut, false otherwise + */ + public boolean isDirectory() { + return isDirectory; + } + + /** + * Gets all the bytes from an InputStream + * @param in the InputStream from which to read bytes + * @return array of all the bytes contained in 'in' + * @throws IOException if an IOException is encountered while reading the data from the InputStream + */ + private static byte[] getBytes(InputStream in) throws IOException { + return getBytes(in, null); + } + + /** + * Gets up to max bytes from an InputStream + * @param in the InputStream from which to read bytes + * @param max maximum number of bytes to read + * @return array of all the bytes contained in 'in' + * @throws IOException if an IOException is encountered while reading the data from the InputStream + */ + private static byte[] getBytes(InputStream in, Integer max) throws IOException { + // read the entire file into a byte buffer + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + byte[] buff = new byte[256]; + while (max == null || max > 0) { + int n = in.read(buff); + if (n == -1) { + break; + } + bout.write(buff, 0, n); + if (max != null) + max -= n; + } + in.close(); + return bout.toByteArray(); + } + + private static boolean isMagicPresent(byte[] link) { + final int magic = 0x0000004C; + final int magic_offset = 0x00; + return link.length >= 32 && bytesToDword(link, magic_offset) == magic; + } + + /** + * Gobbles up link data by parsing it and storing info in member fields + * @param link all the bytes from the .lnk file + */ + private void parseLink(byte[] link) throws ParseException { + try { + if (!isMagicPresent(link)) + throw new ParseException("Invalid shortcut; magic is missing", 0); + + // get the flags byte + byte flags = link[0x14]; + + // get the file attributes byte + final int file_atts_offset = 0x18; + byte file_atts = link[file_atts_offset]; + byte is_dir_mask = (byte)0x10; + if ((file_atts & is_dir_mask) > 0) { + isDirectory = true; + } else { + isDirectory = false; + } + + // if the shell settings are present, skip them + final int shell_offset = 0x4c; + final byte has_shell_mask = (byte)0x01; + int shell_len = 0; + if ((flags & has_shell_mask) > 0) { + // the plus 2 accounts for the length marker itself + shell_len = bytesToWord(link, shell_offset) + 2; + } + + // get to the file settings + int file_start = 0x4c + shell_len; + + final int file_location_info_flag_offset_offset = 0x08; + int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset]; + isLocal = (file_location_info_flag & 2) == 0; + // get the local volume and local system values + //final int localVolumeTable_offset_offset = 0x0C; + final int basename_offset_offset = 0x10; + final int networkVolumeTable_offset_offset = 0x14; + final int finalname_offset_offset = 0x18; + int finalname_offset = link[file_start + finalname_offset_offset] + file_start; + String finalname = getNullDelimitedString(link, finalname_offset); + if (isLocal) { + int basename_offset = link[file_start + basename_offset_offset] + file_start; + String basename = getNullDelimitedString(link, basename_offset); + real_file = basename + finalname; + } else { + int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start; + int shareName_offset_offset = 0x08; + int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset] + + networkVolumeTable_offset; + String shareName = getNullDelimitedString(link, shareName_offset); + real_file = shareName + "\\" + finalname; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParseException("Could not be parsed, probably not a valid WindowsShortcut", 0); + } + } + + private static String getNullDelimitedString(byte[] bytes, int off) { + int len = 0; + // count bytes until the null character (0) + while (true) { + if (bytes[off + len] == 0) { + break; + } + len++; + } + return new String(bytes, off, len); + } + + /* + * convert two bytes into a short note, this is little endian because it's + * for an Intel only OS. + */ + private static int bytesToWord(byte[] bytes, int off) { + return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); + } + + private static int bytesToDword(byte[] bytes, int off) { + return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off); + } + +} -- 1.7.10.2