Analisador de atalho do Windows (.lnk) em Java?

Atualmente estou usando Win32ShellFolderManager2 e ShellFolder.getLinkLocation para resolver atalhos do Windows em Java. Infelizmente, se o programa Java estiver sendo executado como um serviço no Vista, getLinkLocation , isso não funcionará. Especificamente, recebo uma exceção informando “Não foi possível obter a lista de IDs de pastas do shell”.

Pesquisando na web, aparecem menções a esta mensagem de erro, mas sempre em conexão com o JFileChooser . Eu não estou usando o JFileChooser , só preciso resolver um arquivo .lnk para o seu destino.

Alguém sabe de um analisador de terceiros para arquivos .lnk escritos em Java eu ​​poderia usar?

Eu já encontrei documentação não oficial para o formato .lnk aqui , mas eu prefiro não ter que fazer o trabalho se alguém já fez isso antes, já que o formato é bastante assustador.

Adicionado comentários (algumas explicações, bem como crédito para cada colaborador até agora), verificação adicional no arquivo mágico, um teste rápido para ver se um determinado arquivo pode ser um link válido (sem ler todos os bytes), uma correção para lançar um ParseException com mensagem apropriada em vez de ArrayIndexOutOfBoundsException se o arquivo é muito pequeno, fez alguma limpeza geral.

Fonte aqui (se você tiver alguma alteração, envie-a diretamente para o repository / projeto do 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 <jessehager@iname.com> * 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); } } 

A solução de Sam Brightman é apenas para arquivos locais. Eu adicionei suporte para arquivos de rede:

  • Analisador de atalho do Windows (.lnk) em 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; } } 

Eu também trabalhei (agora não tenho tempo para isso) em ‘.lnk’ em Java. Meu código está aqui

É um pouco confuso (alguns testes de lixo), mas a análise local e de rede funciona bem. A criação de links também é implementada. Por favor, teste e me envie correções.

Exemplo de análise:

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

Criando novo link:

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

Eu posso recomendar este repository no GitHub:

https://github.com/BlackOverlord666/mslinks

Lá eu encontrei uma solução simples para criar atalhos:

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

Se você quiser ler atalhos:

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

Se você quiser alterar o ícone do atalho, use:

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

Você pode editar a maioria das propriedades do atalho, como diretório de trabalho, texto de dica de ferramenta, ícone, argumentos de linha de comando, teclas de atalho, criar links para arquivos e diretórios compartilhados da LAN e muito mais …

Espero que isso ajude você 🙂

Atenciosamente Josua Frank

O code plan9assembler vinculado parece funcionar com pequenas modificações. Eu acho que é apenas o ” & 0xff ” para evitar extensão de sinal quando os byte são upcast para int s na function bytes2short que precisam ser alterados. Eu adicionei a funcionalidade descrita em http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf para concatenar a “parte final do nome do caminho”, embora, na prática, isso não pareça ser usado em meus exemplos . Eu não adicionei nenhuma verificação de erro ao header nem lidei com compartilhamentos de rede. Aqui está o que estou usando agora:

 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); } } 

O código dado funciona bem, mas tem um bug. Um byte java é um valor assinado de -128 a 127. Queremos um valor não assinado de 0 a 255 para obter os resultados corretos. Apenas mude a function bytes2short da seguinte forma:

 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; } 

A solução de @Code Bling não funciona para mim no diretório Arquivos do usuário.
Por exemplo, “C: /Users/Username/Filename.txt”.
A razão para isso é: em The_Windows_Shortcut_File_Format.pdf

que foi mencionado por @Stefan Cordes na página 6, ele diz que apenas os primeiros 2 bits são importantes para informações de volumes. Todos os outros bits podem ser preenchidos com lixo random quando o primeiro bit de informação de volume for “0”.

Então, se se trata de:

 isLocal = (file_location_info_flag & 2) == 0; 

então file_location_info_flag pode ser “3”. Este arquivo ainda é local, mas esta linha de código atribui false a isLocal .

Então eu sugiro o seguinte ajuste para o código do @Code Bling:

 isLocal = (file_location_info_flag & 1) == 1; 

Este código curto é realmente útil …

Mas duas correções são necessárias:

  • o isPotentialValidLink foi melhorado para não carregar o arquivo se o nome não terminar com “.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; } 
  • o offset tem que ser computado com 32bits e não apenas um 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; 
Intereting Posts