parse shortcuts without native code

This commit is contained in:
Luke Pulverenti 2013-10-01 15:18:52 -04:00
parent 6cdfa7b0fa
commit 8f002964fb

View File

@ -108,7 +108,7 @@ namespace MediaBrowser.Controller.IO
} }
var builder = new StringBuilder(filename); var builder = new StringBuilder(filename);
foreach (var c in InvalidFileNameChars) foreach (var c in InvalidFileNameChars)
{ {
builder = builder.Replace(c, SpaceChar); builder = builder.Replace(c, SpaceChar);
@ -129,15 +129,17 @@ namespace MediaBrowser.Controller.IO
{ {
throw new ArgumentNullException("filename"); throw new ArgumentNullException("filename");
} }
var link = new ShellLink(); return new WindowsShortcut(filename).ResolvedPath;
((IPersistFile)link).Load(filename, NativeMethods.STGM_READ);
// TODO: if I can get hold of the hwnd call resolve first. This handles moved and renamed files. //var link = new ShellLink();
// ((IShellLinkW)link).Resolve(hwnd, 0) //((IPersistFile)link).Load(filename, NativeMethods.STGM_READ);
var sb = new StringBuilder(NativeMethods.MAX_PATH); //// TODO: if I can get hold of the hwnd call resolve first. This handles moved and renamed files.
WIN32_FIND_DATA data; //// ((IShellLinkW)link).Resolve(hwnd, 0)
((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0); //var sb = new StringBuilder(NativeMethods.MAX_PATH);
return sb.ToString(); //WIN32_FIND_DATA data;
//((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0);
//return sb.ToString();
} }
/// <summary> /// <summary>
@ -157,7 +159,7 @@ namespace MediaBrowser.Controller.IO
{ {
throw new ArgumentNullException("target"); throw new ArgumentNullException("target");
} }
var link = new ShellLink(); var link = new ShellLink();
((IShellLinkW)link).SetPath(target); ((IShellLinkW)link).SetPath(target);
@ -177,7 +179,7 @@ namespace MediaBrowser.Controller.IO
{ {
throw new ArgumentNullException("filename"); throw new ArgumentNullException("filename");
} }
return string.Equals(Path.GetExtension(filename), ".lnk", StringComparison.OrdinalIgnoreCase); return string.Equals(Path.GetExtension(filename), ".lnk", StringComparison.OrdinalIgnoreCase);
} }
@ -244,4 +246,122 @@ namespace MediaBrowser.Controller.IO
return values; return values;
} }
} }
public class WindowsShortcut
{
public bool IsDirectory { get; private set; }
public bool IsLocal { get; private set; }
public string ResolvedPath { get; private set; }
public WindowsShortcut(string file)
{
ParseLink(File.ReadAllBytes(file));
}
private static bool isMagicPresent(byte[] link)
{
const int magic = 0x0000004C;
const 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)
{
if (!isMagicPresent(link))
throw new IOException("Invalid shortcut; magic is missing", 0);
// get the flags byte
byte flags = link[0x14];
// get the file attributes byte
const 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
const int shell_offset = 0x4c;
const 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;
const 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;
const int basename_offset_offset = 0x10;
const int networkVolumeTable_offset_offset = 0x14;
const 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);
ResolvedPath = 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);
ResolvedPath = 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 Encoding.UTF8.GetString(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);
}
}
} }