class WinLnk

This is a library to parse Windows Shell Link (shortcut or .lnk) files on non-Windows systems.

Constants

ATTR_ARCHIVE
ATTR_COMPRESSED
ATTR_DIRECTORY
ATTR_ENCRYPTED
ATTR_HIDDEN
ATTR_NORMAL
ATTR_NOT_CONTENT_INDEXED
ATTR_OFFLINE
ATTR_READONLY
ATTR_REPARSE_POINT
ATTR_SPARSE_FILE
ATTR_SYSTEM
ATTR_TEMPORARY
CLSID
FLAG_HAS_ARGUMENTS
FLAG_HAS_ICON_LOCATION
FLAG_HAS_NAME
FLAG_HAS_RELATIVE_PATH
FLAG_HAS_WORKING_DIR
FLAG_IS_UNICODE
LI_FLAG_LOCAL
LI_FLAG_NETWORK
MAGIC
SW_SHOWMAXIMIZED
SW_SHOWMINNOACTIVE
SW_SHOWNORMAL

Attributes

arguments[R]

Returns the command-line arguments specified when activating the link target.

atime[R]

Returns the access time of the link target.

attributes[R]

Returns the attributes of the link target.

btime[R]

Returns the creation time of the link target.

description[R]

Returns the description of the link.

file_size[R]

Returns the least significant 32 bits of the size of the link target.

flags[R]

Returns the LinkFlags of the link.

hot_key[R]

Returns the shortcut key to activate the application launched by the link.

icon_index[R]

Returns the index of the icon within the given icon location.

icon_location[R]

Returns the location of the icon.

mtime[R]

Returns the write time of the link target.

path[R]

Returns the path pointed to by the link.

relative_path[R]

Returns the path of the link target relative to the link.

show_cmd[R]

Returns the expected window state of an application launched by the link.

working_directory[R]

Returns the working directory used when activating the link target.

Public Class Methods

new(pathname, codepage) click to toggle source

Parses a shell link file given by pathname and returns a WinLnk object. The encoding of non-Unicode strings is assumed to be codepage.

# File lib/winlnk.rb, line 101
def initialize(pathname, codepage)
  @codepage = codepage
  # Do not use Kernel.#open or File.read, which is actually IO.read,
  # because they can open pipes.  File.read was fixed to not open pipes
  # in Ruby 2.6.
  @data = File.open(pathname, mode: "rb:ASCII-8BIT") { |f| f.read }
  off = read_header()
  printf("Link flags: %b\n", @flags) if @@debug

  if @flags & FLAG_HAS_LINK_TARGET_ID_LIST != 0
    off = read_id_list(off)
  end

  if @flags & FLAG_HAS_LINK_INFO != 0
    off = read_link_info(off)
  end

  if @flags & FLAG_HAS_NAME != 0
    @description, off = read_string(off)
  end
  if @flags & FLAG_HAS_RELATIVE_PATH != 0
    @relative_path, off = read_string(off)
  end
  if @flags & FLAG_HAS_WORKING_DIR != 0
    @working_directory, off = read_string(off)
  end
  if @flags & FLAG_HAS_ARGUMENTS != 0
    @arguments, off = read_string(off)
  end
  if @flags & FLAG_HAS_ICON_LOCATION != 0
    @icon_location, off = read_string(off)
  end

  remove_instance_variable(:@data)
end

Private Instance Methods

asciz(off) click to toggle source
# File lib/winlnk.rb, line 225
def asciz(off)
  # Check if "@#{off}" does not go out of the string.
  raise ParseError.new("Truncated file") if @data.size < off
  str = @data.unpack("@#{off} Z*")[0]
  # Check if the terminating null character existed.
  raise ParseError.new("Truncated file") if @data.size - off <= str.bytesize
  return str.force_encoding(@codepage)
end
data(off, len) click to toggle source
# File lib/winlnk.rb, line 245
def data(off, len)
  raise ParseError.new("Truncated file") if @data.size < off + len
  return @data[off, len]
end
filetime2posixtime(filetime) click to toggle source
# File lib/winlnk.rb, line 152
def filetime2posixtime(filetime)
  # If the value is 0, the time is not set.
  return nil if filetime == 0
  # Windows FILETIME is the time from 1601-01-01 in 100-nanosecond unit.
  filetime -= 116444736000000000
  return Time.at(filetime / 10000000, filetime % 10000000 / 10)
end
make_encodings_be_compatible(a, b) click to toggle source
# File lib/winlnk.rb, line 250
def make_encodings_be_compatible(a, b)
  return if Encoding::compatible?(a, b)
  a.encode!("UTF-8")
  b.encode!("UTF-8")
end
read_header() click to toggle source
# File lib/winlnk.rb, line 139
def read_header()
  raise ParseError.new("Not a shell link file") if data(0x00, 4) != MAGIC
  raise ParseError.new("CLSID mismatch") if data(0x04, 16) != CLSID
  @flags, @attributes = data(0x14, 8).unpack("V2")
  times = data(0x1c, 24).unpack("V6")
  @btime = filetime2posixtime(times[1] << 32 | times[0])
  @atime = filetime2posixtime(times[3] << 32 | times[2])
  @mtime = filetime2posixtime(times[5] << 32 | times[4])
  @file_size, @icon_index, @show_cmd, @hot_key = data(0x34, 14).unpack("V3v")
  _reserved = data(0x42, 10).unpack("vV2")
  return 0x4c
end
read_id_list(off) click to toggle source
# File lib/winlnk.rb, line 160
def read_id_list(off)
  @id_list = []
  len, = data(off, 2).unpack("v")
  off += 2
  nextoff = off + len
  loop do
    itemlen, = data(off, 2).unpack("v")
    return nextoff if itemlen == 0
    @id_list.push(data(off + 2, itemlen - 2))
    off += itemlen
  end
end
read_string(off) click to toggle source
# File lib/winlnk.rb, line 213
def read_string(off)
  len, = data(off, 2).unpack("v")
  if @flags & FLAG_IS_UNICODE != 0
    # UTF-16.
    len *= 2
    return data(off + 2, len).encode("UTF-8", "UTF-16LE"), off + len + 2
  else
    # The system default code page.
    return data(off + 2, len).force_encoding(@codepage), off + len + 2
  end
end
utf16z(off) click to toggle source
# File lib/winlnk.rb, line 234
def utf16z(off)
  zz = off
  while @data.size >= zz + 2
    if @data.getbyte(zz) == 0 && @data.getbyte(zz + 1) == 0
      return @data[off...zz].encode!("UTF-8", "UTF-16LE")
    end
    zz += 2
  end
  raise ParseError.new("Truncated file")
end