summaryrefslogtreecommitdiff
path: root/apps/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins')
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/viewers.config1
-rw-r--r--apps/plugins/windows_lnk.c344
3 files changed, 346 insertions, 0 deletions
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 6551df71a9..9e153a3c96 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -34,6 +34,7 @@ flipit.c
34shopper.c 34shopper.c
35resistor.c 35resistor.c
36otp.c 36otp.c
37windows_lnk.c
37 38
38#ifdef USB_ENABLE_HID 39#ifdef USB_ENABLE_HID
39remote_control.c 40remote_control.c
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config
index c938eeb275..96228085c9 100644
--- a/apps/plugins/viewers.config
+++ b/apps/plugins/viewers.config
@@ -100,3 +100,4 @@ z6,viewers/frotz,-
100z7,viewers/frotz,- 100z7,viewers/frotz,-
101z8,viewers/frotz,- 101z8,viewers/frotz,-
102shopper,viewers/shopper,1 102shopper,viewers/shopper,1
103lnk,viewers/windows_lnk,-
diff --git a/apps/plugins/windows_lnk.c b/apps/plugins/windows_lnk.c
new file mode 100644
index 0000000000..6ef1a6a3ac
--- /dev/null
+++ b/apps/plugins/windows_lnk.c
@@ -0,0 +1,344 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2017 Sebastian Leonhardt
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "plugin.h"
23
24/**
25 * Follow Windows shortcuts (*.lnk files) in Rockbox.
26 * If the destination is a file, it will be selected in the file browser,
27 * a directory will be entered.
28 * For now, only relative links are supported.
29 */
30
31/* a selection of link flags */
32#define HAS_LINK_TARGET_ID_LIST 0x01
33#define HAS_LINK_INFO 0x02
34#define HAS_NAME 0x04
35#define HAS_RELATIVE_PATH 0x08
36#define HAS_WORKING_DIR 0x10
37#define HAS_ARGUMENTS 0x20
38#define HAS_ICON_LOCATION 0x40
39#define IS_UNICODE 0x80
40#define FORCE_NO_LINK_INFO 0x100
41/* a selection of file attributes flags */
42#define FILE_ATTRIBUTE_DIRECTORY 0x10
43#define FILE_ATTRIBUTE_NORMAL 0x80
44
45
46/**
47 * Read one byte from file
48 * \param fd file descriptor
49 * \param *a where the data should go
50 * \return false if an error occured, true on success
51 */
52static bool read_byte(const int fd, unsigned char *a)
53{
54 if (!rb->read(fd, a, 1))
55 return false;
56 return true;
57}
58
59/**
60 * Read 16-bit word from file, respecting windows endianness (little endian)
61 * \param fd file descriptor
62 * \param *a where the data should go
63 * \return false if an error occured, true on success
64 */
65static bool read_word(const int fd, int *a)
66{
67 unsigned char a1,a2;
68 int r;
69
70 r = read_byte(fd, &a1);
71 if (!r)
72 return false;
73 r = read_byte(fd, &a2);
74 if (!r)
75 return false;
76 *a = (a2<<8) + a1;
77 return true;
78}
79
80/**
81 * Read 32-bit word from file, respecting windows endianness (little endian)
82 * \param fd file descriptor
83 * \param *a where the data should go
84 * \return false if an error occured, true on success
85 */
86static bool read_lword(const int fd, uint32_t *a)
87{
88 int a1,a2;
89 int r;
90
91 r = read_word(fd, &a1);
92 if (!r)
93 return false;
94 r = read_word(fd, &a2);
95 if (!r)
96 return false;
97 *a = (a2<<16) + a1;
98 return true;
99}
100
101
102/**
103 * Scan *.lnk file for relative link target
104 * \param fd file descriptor
105 * \param link_target the extracted link destination
106 * \param target_size available space for the extracted link (in bytes)
107 * \param link_flags the link flags are stored here
108 * \param file_atts file attributes are stored here
109 * \return returns false if extraction failed.
110 */
111static bool extract_link_destination(const int fd,
112 char *link_target, const int target_size,
113 uint32_t *link_flags, uint32_t *file_atts)
114{
115 int r;
116
117 /* Read ShellLinkHeader */
118 uint32_t size;
119 r = read_lword(fd, &size);
120 if (!r) return false;
121 if (size!=0x4c) { /* header size MUST be 76 bytes */
122 DEBUGF("unexpected header size 0x%08lx (must be 0x0000004c)\n", size);
123 return false;
124 }
125
126 /* Skip LinkCLSID (class identifier) */
127 rb->lseek(fd, 0x10, SEEK_CUR);
128 /* We need the LinkFlags and File attribute (to see if target is a directory) */
129 r = read_lword(fd, link_flags);
130 if (!r) return false;
131 r = read_lword(fd, file_atts);
132 if (!r) return false;
133 rb->lseek(fd, size, SEEK_SET); /* Skip to end of header */
134
135 /* For now we only support relative links, so we can exit right away
136 if no relative link structure is present */
137 if (!(*link_flags & HAS_RELATIVE_PATH)) {
138 DEBUGF("Link doesn't have relative path information\n");
139 return false;
140 }
141
142 /* Read (skip) LinkTargetIDList structure if present */
143 if (*link_flags & HAS_LINK_TARGET_ID_LIST) {
144 int size;
145 if (!read_word(fd, &size))
146 return false;
147 rb->lseek(fd, size, SEEK_CUR);
148 }
149
150 /* Read (skip) LinkInfo structure if present */
151 if (*link_flags & HAS_LINK_INFO) {
152 uint32_t size;
153 r = read_lword(fd, &size);
154 if (!r) return false;
155 rb->lseek(fd, size-4, SEEK_CUR);
156 }
157
158 /* String Data section */
159
160 /* Read (skip) NAME_STRING StringData structure if present */
161 if (*link_flags & HAS_NAME) {
162 int ccount;
163 if (!read_word(fd, &ccount))
164 return false;
165 if (*link_flags & IS_UNICODE)
166 rb->lseek(fd, ccount*2, SEEK_CUR);
167 else
168 rb->lseek(fd, ccount, SEEK_CUR);
169 }
170
171 /* Read RELATIVE_PATH StringData structure if present */
172 /* This is finally the data we are searching for! */
173 if (*link_flags & HAS_RELATIVE_PATH) {
174 int ccount;
175 r = read_word(fd, &ccount);
176 if (*link_flags & IS_UNICODE) {
177 unsigned char utf16[4], utf8[10];
178 link_target[0] = '\0';
179 for (int i=0; i<ccount; ++i) {
180 r = read_byte(fd, &utf16[0]);
181 if (!r) return false;
182 r = read_byte(fd, &utf16[1]);
183 if (!r) return false;
184 /* check for surrogate pair and read the second char */
185 if (utf16[1] >= 0xD8 && utf16[1] < 0xE0) {
186 r = read_byte(fd, &utf16[2]);
187 if (!r) return false;
188 r = read_byte(fd, &utf16[3]);
189 if (!r) return false;
190 ++i;
191 }
192 char *ptr = rb->utf16LEdecode(utf16, utf8, 1);
193 *ptr = '\0';
194 rb->strlcat(link_target, utf8, target_size);
195 }
196 }
197 else { /* non-unicode */
198 if (ccount >= target_size) {
199 DEBUGF("ERROR: link target filename exceeds size!");
200 return false;
201 }
202 rb->read(fd, link_target, ccount);
203 link_target[ccount] = '\0';
204 }
205 }
206
207 /* convert from windows to unix subdir separators */
208 for (int i=0; link_target[i] != '\0'; ++i) {
209 if (link_target[i]=='\\')
210 link_target[i] = '/';
211 }
212
213 return true;
214}
215
216
217/**
218 * strip rightmost part of file/pathname to next '/', i.e. remove filename
219 * or last subdirectory. Leaves a trailing '/' character.
220 * \param pathname full path or filename
221*/
222static void strip_rightmost_part(char *pathname)
223{
224 for (int i = rb->strlen(pathname)-2; i >= 0; --i) {
225 if (pathname[i] == '/') {
226 pathname[i+1] = '\0'; /* cut off */
227 return;
228 }
229 }
230 pathname[0] = '\0';
231}
232
233
234/**
235 * Combine link file's absolute path with relative link target to form
236 * (absolute) link destination
237 * \param abs_path full shortcut filename (including path)
238 * \param rel_path the extracted relative link target
239 * \param max_len maximum lengt of combined filename
240 */
241static void assemble_link_dest(char *const abs_path, char const *rel_path,
242 const size_t max_len)
243{
244 strip_rightmost_part(abs_path); /* cut off link filename */
245
246 for (;;) {
247 if (rb->strncmp(rel_path, "../", 3)==0) {
248 rel_path += 3;
249 strip_rightmost_part(abs_path);
250 }
251 else if (rb->strncmp(rel_path, "./", 2)==0) {
252 rel_path += 2;
253 }
254 else
255 break;
256 }
257
258 if (*rel_path=='/')
259 ++rel_path; /* avoid double '/' chars when concatenating */
260 rb->strlcat(abs_path, rel_path, max_len);
261}
262
263
264/**
265 * Select the chosen file in the file browser. A directory (filename ending
266 * with '/') will be entered.
267 * \param link_target link target to be selected in the browser
268 * \return returns false if the target doesn't exist
269 */
270static bool goto_entry(char *link_target)
271{
272 DEBUGF("Trying to go to '%s'...\n", link_target);
273 if (!rb->file_exists(link_target))
274 return false;
275
276 /* Set the browsers dirfilter to the global setting.
277 * This is required in case the plugin was launched
278 * from the plugins browser, in which case the
279 * dirfilter is set to only display .rock files */
280 rb->set_dirfilter(rb->global_settings->dirfilter);
281
282 /* Change directory to the entry selected by the user */
283 rb->set_current_file(link_target);
284 return true;
285}
286
287
288enum plugin_status plugin_start(const void* void_parameter)
289{
290 char *link_filename;
291 char extracted_link[MAX_PATH];
292 char link_target[MAX_PATH];
293 uint32_t lnk_flags;
294 uint32_t file_atts;
295
296 /* This is a viewer, so a parameter must have been specified */
297 if (void_parameter == NULL) {
298 rb->splash(HZ*3, "No *.lnk file selected");
299 return PLUGIN_OK;
300 }
301
302 link_filename = (char*)void_parameter;
303 DEBUGF("Shortcut filename: \"%s\"\n", link_filename);
304
305 int fd = rb->open(link_filename, O_RDONLY);
306 if (fd < 0) {
307 DEBUGF("Can't open link file\n");
308 rb->splashf(HZ*3, "Can't open link file!");
309 return PLUGIN_OK;
310 }
311
312 if (!extract_link_destination(fd, extracted_link, sizeof(extracted_link),
313 &lnk_flags, &file_atts)) {
314 rb->close(fd);
315 DEBUGF("Error in extract_link_destination()\n");
316 rb->splashf(HZ*3, "Unsupported or erroneous link file format");
317 return PLUGIN_OK;
318 }
319 rb->close(fd);
320 DEBUGF("Shortcut destination: \"%s\"\n", extracted_link);
321
322 rb->strcpy(link_target, link_filename);
323 assemble_link_dest(link_target, extracted_link, sizeof(link_target));
324 DEBUGF("Link absolute path: \"%s\"\n", link_target);
325
326 /* if target is a directory, add '/' to the dir name,
327 so that the directory gets entered instead of just highlighted */
328 if (file_atts & FILE_ATTRIBUTE_DIRECTORY)
329 if (link_target[rb->strlen(link_target)-1] != '/')
330 rb->strlcat(link_target, "/", sizeof(link_target));
331
332 if (!goto_entry(link_target)) {
333 char *what;
334 if (file_atts & FILE_ATTRIBUTE_DIRECTORY)
335 what = "directory";
336 else
337 what = "file";
338 rb->splashf(HZ*3, "Can't find %s %s", what, link_target);
339 DEBUGF("Can't find %s %s", what, link_target);
340 return PLUGIN_OK;
341 }
342
343 return PLUGIN_OK;
344}