2
0
Эх сурвалжийг харах

parse shortcuts without native code

Luke Pulverenti 11 жил өмнө
parent
commit
8f002964fb

+ 132 - 12
MediaBrowser.Controller/IO/FileSystem.cs

@@ -108,7 +108,7 @@ namespace MediaBrowser.Controller.IO
             }
 
             var builder = new StringBuilder(filename);
-            
+
             foreach (var c in InvalidFileNameChars)
             {
                 builder = builder.Replace(c, SpaceChar);
@@ -129,15 +129,17 @@ namespace MediaBrowser.Controller.IO
             {
                 throw new ArgumentNullException("filename");
             }
-            
-            var link = new ShellLink();
-            ((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.  
-            // ((IShellLinkW)link).Resolve(hwnd, 0) 
-            var sb = new StringBuilder(NativeMethods.MAX_PATH);
-            WIN32_FIND_DATA data;
-            ((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0);
-            return sb.ToString();
+
+            return new WindowsShortcut(filename).ResolvedPath;
+
+            //var link = new ShellLink();
+            //((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.  
+            //// ((IShellLinkW)link).Resolve(hwnd, 0) 
+            //var sb = new StringBuilder(NativeMethods.MAX_PATH);
+            //WIN32_FIND_DATA data;
+            //((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0);
+            //return sb.ToString();
         }
 
         /// <summary>
@@ -157,7 +159,7 @@ namespace MediaBrowser.Controller.IO
             {
                 throw new ArgumentNullException("target");
             }
-            
+
             var link = new ShellLink();
 
             ((IShellLinkW)link).SetPath(target);
@@ -177,7 +179,7 @@ namespace MediaBrowser.Controller.IO
             {
                 throw new ArgumentNullException("filename");
             }
-            
+
             return string.Equals(Path.GetExtension(filename), ".lnk", StringComparison.OrdinalIgnoreCase);
         }
 
@@ -244,4 +246,122 @@ namespace MediaBrowser.Controller.IO
             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);
+        }
+
+    }
 }