winlnk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/README +26 -0
  3. data/Rakefile +7 -0
  4. data/lib/winlnk.rb +201 -0
  5. data/test/test_error.rb +42 -0
  6. metadata +49 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d699909354195807a0b6d300658026db1e52e616
4
+ data.tar.gz: b8e3fd8963ef84caed6b84a7c63d403cb146e5b9
5
+ SHA512:
6
+ metadata.gz: 7280505f2676cc21b09add97b80cd3b1930e485868ac6eab1ec1aef497e2d257972a6794d200fa8423414defc79d4f4b014dd8dfee0055b746188807f0fd53c7
7
+ data.tar.gz: 9ef3ed9c34971b91ae4761981edea2ec100b3bbbc59cd9312bff95e98db13cc439bd3ae5d5ee56fc5372e3879d049a70158cc0418de24bf44bdc9da048c9c396
data/README ADDED
@@ -0,0 +1,26 @@
1
+ Library to read Windows Shell Link (shortcut or .lnk) files
2
+ -----------------------------------------------------------
3
+
4
+ This is a library to parse Windows Shell Link (shortcut or .lnk) files
5
+ on non-Windows systems.
6
+
7
+ Installation
8
+ ------------
9
+
10
+ With Gem:
11
+
12
+ # gem install winlnk
13
+
14
+ From the source:
15
+
16
+ % rake test
17
+
18
+ Copy the contents of the lib directory into somewhere you like.
19
+
20
+ Usage
21
+ -----
22
+
23
+ To read the link target, create an instance and get its path attribute.
24
+
25
+ link = WinLnk.new("path/to/link.lnk")
26
+ p link.path
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "rake/testtask"
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList["test/test_*.rb"]
7
+ end
data/lib/winlnk.rb ADDED
@@ -0,0 +1,201 @@
1
+ #
2
+ # Copyright (c) 2014 KAMADA Ken'ichi.
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions
7
+ # are met:
8
+ # 1. Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright
11
+ # notice, this list of conditions and the following disclaimer in the
12
+ # documentation and/or other materials provided with the distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
+ # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
+ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
+ # SUCH DAMAGE.
25
+ #
26
+
27
+ # This is a library to parse Windows Shell Link (shortcut or .lnk) files
28
+ # on non-Windows systems.
29
+ class WinLnk
30
+ MAGIC = "\x4c\x00\x00\x00".b
31
+ CLSID = "\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46".b
32
+
33
+ FLAG_HAS_LINK_TARGET_ID_LIST = 1 << 0
34
+ FLAG_HAS_LINK_INFO = 1 << 1
35
+ FLAG_HAS_NAME = 1 << 2
36
+ FLAG_HAS_RELATIVE_PATH = 1 << 3
37
+ FLAG_HAS_WORKING_DIR = 1 << 4
38
+ FLAG_HAS_ARGUMENTS = 1 << 5
39
+ FLAG_HAS_ICON_LOCATION = 1 << 6
40
+ FLAG_IS_UNICODE = 1 << 7
41
+
42
+ ATTR_READONLY = 1 << 0
43
+ ATTR_HIDDEN = 1 << 1
44
+ ATTR_SYSTEM = 1 << 2
45
+ ATTR_DIRECTORY = 1 << 4
46
+ ATTR_ARCHIVE = 1 << 5
47
+ ATTR_NORMAL = 1 << 7
48
+ ATTR_TEMPORARY = 1 << 8
49
+ ATTR_SPARSE_FILE = 1 << 9
50
+ ATTR_REPARSE_POINT = 1 << 10
51
+ ATTR_COMPRESSED = 1 << 11
52
+ ATTR_OFFLINE = 1 << 12
53
+ ATTR_NOT_CONTENT_INDEXED = 1 << 13
54
+ ATTR_ENCRYPTED = 1 << 14
55
+
56
+ SW_SHOWNORMAL = 1
57
+ SW_SHOWMAXIMIZED = 3
58
+ SW_SHOWMINNOACTIVE = 7
59
+
60
+ LI_FLAG_LOCAL = 1 << 0
61
+ LI_FLAG_NETWORK = 1 << 1
62
+
63
+ @@debug = nil
64
+
65
+ # Returns the LinkFlags of the link.
66
+ attr_reader :flags
67
+ # Returns the path pointed to by the link.
68
+ attr_reader :path
69
+ # Returns the description of the link.
70
+ attr_reader :description
71
+ # Returns the path of the link target relative to the link.
72
+ attr_reader :relative_path
73
+ # Returns the working directory used when activating the link target.
74
+ attr_reader :working_directory
75
+ # Returns the command-line arguments specified when activating
76
+ # the link target.
77
+ attr_reader :arguments
78
+ # Returns the location of the icon.
79
+ attr_reader :icon_location
80
+
81
+ # Parses a shell link file given by +pathname+ and returns
82
+ # a +WinLnk+ object. The encoding of non-Unicode strings is assumed
83
+ # to be +codepage+.
84
+ def initialize(pathname, codepage)
85
+ @codepage = codepage
86
+ @data = open(pathname, "rb:ASCII-8BIT") { |f| f.read }
87
+ off = read_header()
88
+ printf("Link flags: %b\n", @flags) if @@debug
89
+
90
+ if @flags & FLAG_HAS_LINK_TARGET_ID_LIST != 0
91
+ off = read_id_list(off)
92
+ end
93
+
94
+ if @flags & FLAG_HAS_LINK_INFO != 0
95
+ off = read_link_info(off)
96
+ end
97
+
98
+ if @flags & FLAG_HAS_NAME != 0
99
+ @description, off = read_string(off)
100
+ end
101
+ if @flags & FLAG_HAS_RELATIVE_PATH != 0
102
+ @relative_path, off = read_string(off)
103
+ end
104
+ if @flags & FLAG_HAS_WORKING_DIR != 0
105
+ @working_directory, off = read_string(off)
106
+ end
107
+ if @flags & FLAG_HAS_ARGUMENTS != 0
108
+ @arguments, off = read_string(off)
109
+ end
110
+ if @flags & FLAG_HAS_ICON_LOCATION != 0
111
+ @icon_location, off = read_string(off)
112
+ end
113
+
114
+ remove_instance_variable(:@data)
115
+ end
116
+
117
+ private
118
+
119
+ def read_header()
120
+ raise ParseError.new("Not a shell link file") if data(0x00, 4) != MAGIC
121
+ raise ParseError.new("CLSID mismatch") if data(0x04, 16) != CLSID
122
+ @flags, @attributes = data(0x14, 8).unpack("V2")
123
+ times = data(0x1c, 24).unpack("V6")
124
+ @btime = filetime2posixtime(times[1] << 32 | times[0])
125
+ @atime = filetime2posixtime(times[3] << 32 | times[2])
126
+ @mtime = filetime2posixtime(times[5] << 32 | times[4])
127
+ @file_size, @icon_index, @show_cmd, @hot_key = data(0x34, 16).unpack("V3v")
128
+ reserved = data(0x44, 8).unpack("vV2")
129
+ return 0x4c
130
+ end
131
+
132
+ def filetime2posixtime(filetime)
133
+ # Windows FILETIME is the time from 1601-01-01 in 100-nanosecond unit.
134
+ filetime -= 116444736000000000
135
+ return Time.at(filetime / 10000000, filetime % 10000000 / 10)
136
+ end
137
+
138
+ def read_id_list(off)
139
+ @id_list = []
140
+ len, = data(off, 2).unpack("v")
141
+ off += 2
142
+ nextoff = off + len
143
+ loop do
144
+ itemlen, = data(off, 2).unpack("v")
145
+ return nextoff if itemlen == 0
146
+ @id_list.push(data(off + 2, itemlen - 2))
147
+ off += itemlen
148
+ end
149
+ end
150
+
151
+ def read_link_info(off)
152
+ len, = data(off, 4).unpack("V")
153
+ raise ParseError.new("Too short LinkInfo") if len < 0x1c
154
+ header_len, li_flags, vol_id_off, base_path_off,
155
+ net_rel_link_off, suffix_off = data(off + 4, 24).unpack("V6")
156
+ if @@debug
157
+ printf("LinkInfo header size: %u\n", header_len)
158
+ printf("LinkInfo flags: %b\n", li_flags)
159
+ end
160
+
161
+ if li_flags & LI_FLAG_LOCAL != 0
162
+ base_path, = data_all(off + base_path_off).unpack("Z*")
163
+ suffix, = data_all(off + suffix_off).unpack("Z*")
164
+ @path = (base_path + suffix).force_encoding(@codepage)
165
+ end
166
+ if li_flags & LI_FLAG_NETWORK != 0
167
+ # Parse the CommonNetworkRelativeLink structure.
168
+ net_name_off, = data(off + net_rel_link_off + 8, 2).unpack("v")
169
+ net_name, = data_all(off + net_rel_link_off + net_name_off).unpack("Z*")
170
+ suffix, = data_all(off + suffix_off).unpack("Z*")
171
+ @path = (net_name + "\\" + suffix).force_encoding(@codepage)
172
+ end
173
+ return off + len
174
+ end
175
+
176
+ def read_string(off)
177
+ len, = data(off, 2).unpack("v")
178
+ if @flags & FLAG_IS_UNICODE != 0
179
+ # UTF-16.
180
+ len *= 2
181
+ return data(off + 2, len).encode("UTF-8", "UTF-16LE"), off + len + 2
182
+ else
183
+ # The system default code page.
184
+ return data(off + 2, len).force_encoding(@codepage), off + len + 2
185
+ end
186
+ end
187
+
188
+ def data(off, len)
189
+ raise ParseError.new("Truncated file") if @data.size < off + len
190
+ return @data[off, len]
191
+ end
192
+
193
+ def data_all(off)
194
+ raise ParseError.new("Truncated file") if @data.size < off
195
+ return @data[off..-1]
196
+ end
197
+
198
+ # This exception is raised when failed to parse a link.
199
+ class ParseError < StandardError
200
+ end
201
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Copyright (c) 2015 KAMADA Ken'ichi.
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions
7
+ # are met:
8
+ # 1. Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright
11
+ # notice, this list of conditions and the following disclaimer in the
12
+ # documentation and/or other materials provided with the distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
+ # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
+ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
+ # SUCH DAMAGE.
25
+ #
26
+
27
+ require "test/unit"
28
+ require "winlnk"
29
+
30
+ class TestError < Test::Unit::TestCase
31
+ def test_enoent
32
+ assert_raise(Errno::ENOENT) do
33
+ WinLnk.new("no_such_file", "US-ASCII")
34
+ end
35
+ end
36
+
37
+ def test_not_lnk
38
+ assert_raise(WinLnk::ParseError) do
39
+ WinLnk.new("test/test_error.rb", "US-ASCII")
40
+ end
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: winlnk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - KAMADA Ken'ichi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-26 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ This is a library to parse Windows Shell Link (shortcut or .lnk) files
15
+ on non-Windows systems.
16
+ email: kamada@nanohz.org
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README
22
+ - Rakefile
23
+ - lib/winlnk.rb
24
+ - test/test_error.rb
25
+ homepage: https://github.com/kamadak/winlnk-rb
26
+ licenses:
27
+ - BSD-2-Clause
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.5.2
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Library to read Windows Shell Link (shortcut or .lnk) files
49
+ test_files: []