Parser di Windows (.lnk) in Java?

Attualmente sto usando Win32ShellFolderManager2 e ShellFolder.getLinkLocation per risolvere i collegamenti di Windows in Java. Sfortunatamente, se il programma Java è in esecuzione come servizio in Vista, getLinkLocation , questo non funziona. Nello specifico, ottengo un’eccezione che afferma “Imansible ottenere l’elenco ID della cartella shell”.

La ricerca nel Web fa apparire menzioni di questo messaggio di errore, ma sempre in connessione con JFileChooser . Non sto usando JFileChooser , ho solo bisogno di risolvere un file .lnk alla sua destinazione.

Qualcuno sa di un parser di terze parti per i file .lnk scritti in Java che potrei usare?

Da allora ho trovato documentazione non ufficiale per il formato .lnk, ma preferirei non dover fare il lavoro se qualcuno lo ha già fatto, dal momento che il formato è piuttosto spaventoso.

Aggiunti commenti (alcune spiegazioni e credito per ogni contributore fino ad ora), controllo aggiuntivo sul file magic, un test rapido per vedere se un determinato file potrebbe essere un link valido (senza leggere tutti i byte), una correzione da lanciare una ParseException con messaggio appropriato al posto di ArrayIndexOutOfBoundsException se il file è troppo piccolo, ha fatto un po ‘di pulizia generale.

Fonte qui (se hai qualche cambiamento, spingili direttamente al repository / progetto GitHub.

 package org.stackoverflowusers.file; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.text.ParseException; /** * Represents a Windows shortcut (typically visible to Java only as a '.lnk' file). * * Retrieved 2011-09-23 from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775 * Originally called LnkParser * * Written by: (the stack overflow users, obviously!) * Apache Commons VFS dependency removed by crysxd (why were we using that!?) https://github.com/crysxd * Headerified, refactored and commented by Code Bling http://stackoverflow.com/users/675721/code-bling * Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes * Adapted by Sam Brightman http://stackoverflow.com/users/2492/sam-brightman * Based on information in 'The Windows Shortcut File Format' by Jesse Hager <[email protected]> * And somewhat based on code from the book 'Swing Hacks: Tips and Tools for Killer GUIs' * by Joshua Marinacci and Chris Adamson * ISBN: 0-596-00907-0 * http://www.oreilly.com/catalog/swinghks/ */ public class WindowsShortcut { private boolean isDirectory; private boolean isLocal; private String real_file; /** * Provides a quick test to see if this could be a valid link ! * If you try to instantiate a new WindowShortcut and the link is not valid, * Exceptions may be thrown and Exceptions are extremely slow to generate, * therefore any code needing to loop through several files should first check this. * * @param file the potential link * @return true if may be a link, false otherwise * @throws IOException if an IOException is thrown while reading from the file */ public static boolean isPotentialValidLink(File file) throws IOException { final int minimum_length = 0x64; InputStream fis = new FileInputStream(file); boolean isPotentiallyValid = false; try { isPotentiallyValid = file.isFile() && file.getName().toLowerCase().endsWith(".lnk") && fis.available() >= 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); } } 

La soluzione di Sam Brightman è solo per i file locali. Ho aggiunto il supporto per i file di rete:

  • Parser di Windows (.lnk) in Java?
  • http://code.google.com/p/8bits/downloads/detail?name=The_Windows_Shortcut_File_Format.pdf
  • http://www.javafaq.nu/java-example-code-468.html

     public class LnkParser { public LnkParser(File f) throws IOException { parse(f); } private boolean isDirectory; private boolean isLocal; public boolean isDirectory() { return isDirectory; } private String real_file; public String getRealFilename() { return real_file; } private void parse(File f) throws IOException { // read the entire file into a byte buffer FileInputStream fin = new FileInputStream(f); ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] buff = new byte[256]; while (true) { int n = fin.read(buff); if (n == -1) { break; } bout.write(buff, 0, n); } fin.close(); byte[] link = bout.toByteArray(); parseLink(link); } private void parseLink(byte[] link) { // 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 = bytes2short(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; } } 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 bytes2short(byte[] bytes, int off) { return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); } /** * Returns the value of the instance variable 'isLocal'. * * @return Returns the isLocal. */ public boolean isLocal() { return isLocal; } } 

Ho anche lavorato (ora non ho tempo per quello) su “.lnk” in Java. Il mio codice è qui

È un po ‘disordinato (alcuni test di spazzatura) ma l’analisi locale e di rete funziona bene. Anche la creazione di collegamenti è implementata. Si prega di testare e inviarmi patch.

Esempio di analisi:

 Shortcut scut = Shortcut.loadShortcut(new File("C:\\t.lnk")); System.out.println(scut.toString()); 

Creare un nuovo link:

 Shortcut scut = new Shortcut(new File("C:\\temp")); OutputStream os = new FileOutputStream("C:\\t.lnk"); os.write(scut.getBytes()); os.flush(); os.close(); 

Posso raccomandare questo repository su GitHub:

https://github.com/BlackOverlord666/mslinks

Lì ho trovato una soluzione semplice per creare scorciatoie:

ShellLink.createLink("path/to/existing/file.txt", "path/to/the/future/shortcut.lnk");

Se vuoi leggere le scorciatoie:

 File shortcut = ...; String pathToExistingFile = new ShellLink(shortcut).resolveTarget(); 

Se vuoi cambiare l’icona della scorciatoia, usa:

 ShellLink sl = ...; sl.setIconLocation("/path/to/icon/file"); 

Puoi modificare la maggior parte delle proprietà del collegamento shortcut come directory di lavoro, testo tooltip, icona, argomenti della riga di comando, tasti di scelta rapida, creare collegamenti a file e directory condivisi LAN e molto altro ancora …

Spero che questo ti aiuti 🙂

Cordiali saluti Josua Frank

Il codice plan9assembler collegato sembra funzionare con modifiche minori. Penso che sia solo il ” & 0xff ” per impedire l’estensione del segno quando i byte s sono trasmessi a int s nella funzione bytes2short che deve essere modificata. Ho aggiunto la funzionalità descritta in http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf per concatenare la “parte finale del percorso” anche se in pratica questo non sembra essere usato nei miei esempi . Non ho aggiunto alcun errore nel controllo dell’intestazione o gestito le condivisioni di rete. Ecco cosa sto usando ora:

 import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.text.DecimalFormat; import java.text.NumberFormat; public class LnkParser { public LnkParser(File f) throws Exception { parse(f); } private boolean is_dir; public boolean isDirectory() { return is_dir; } private String real_file; public String getRealFilename() { return real_file; } private void parse(File f) throws Exception { // read the entire file into a byte buffer FileInputStream fin = new FileInputStream(f); ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] buff = new byte[256]; while (true) { int n = fin.read(buff); if (n == -1) { break; } bout.write(buff, 0, n); } fin.close(); byte[] link = bout.toByteArray(); // 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) { is_dir = true; } else { is_dir = 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 = bytes2short(link, shell_offset) + 2; } // get to the file settings int file_start = 0x4c + shell_len; // get the local volume and local system values final int basename_offset_offset = 0x10; final int finalname_offset_offset = 0x18; int basename_offset = link[file_start + basename_offset_offset] + file_start; int finalname_offset = link[file_start + finalname_offset_offset] + file_start; String basename = getNullDelimitedString(link, basename_offset); String finalname = getNullDelimitedString(link, finalname_offset); real_file = basename + finalname; } 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 bytes2short(byte[] bytes, int off) { return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff); } } 

Il codice dato funziona bene, ma ha un bug. Un byte java è un valore firmato da -128 a 127. Vogliamo un valore senza segno da 0 a 255 per ottenere i risultati corretti. Basta cambiare la funzione bytes2short come segue:

 static int bytes2short(byte[] bytes, int off) { int low = (bytes[off]<0 ? bytes[off]+256 : bytes[off]); int high = (bytes[off+1]<0 ? bytes[off+1]+256 : bytes[off+1])<<8; return 0 | low | high; } 

La soluzione di @Code Bling non funziona per me nella directory User.
Ad esempio “C: /Users/Username/Filename.txt”.
Il motivo è: in The_Windows_Shortcut_File_Format.pdf

che è stato menzionato da @Stefan Cordes a pagina 6, dice che solo i primi 2 bit sono importanti per le informazioni sui volumi. Tutti gli altri bit potrebbero essere riempiti di rifiuti casuali quando il primo bit di informazioni sui volumi è “0”.

Quindi se si tratta di:

 isLocal = (file_location_info_flag & 2) == 0; 

quindi file_location_info_flag potrebbe essere “3”. Questo file è ancora locale ma questa riga di codice assegna false a isLocal .

Quindi suggerisco il seguente aggiustamento al codice di @Code Bling:

 isLocal = (file_location_info_flag & 1) == 1; 

Questo codice breve è davvero utile …

Ma sono necessarie due correzioni:

  • isPotentialValidLink migliorato per non caricare il file se il nome non termina con “.lnk”

      public static boolean isPotentialValidLink(final File file) { final int minimum_length = 0x64; boolean isPotentiallyValid = false; if (file.getName().toLowerCase().endsWith(".lnk")) try (final InputStream fis = new FileInputStream(file)) { isPotentiallyValid = file.isFile() && fis.available() >= minimum_length && isMagicPresent(getBytes(fis, 32)); } catch (Exception e) { // forget it } return isPotentiallyValid; } 
  • l’offset deve essere calcolato con 32 bit non solo un byte …

      final int finalname_offset = bytesToDword(link,file_start + finalname_offset_offset) + file_start; final int basename_offset = bytesToDword(link,file_start + basename_offset_offset) + file_start;