summaryrefslogtreecommitdiffstats
path: root/apps/plugins
diff options
context:
space:
mode:
authorSebastian Leonhardt <sebastian.leonhardt@web.de>2017-09-15 23:43:41 +0200
committerSebastian Leonhardt <sebastian.leonhardt@web.de>2019-08-11 22:26:49 +0200
commit5c701b02809ffeec2e9de50e89995dbb85cd7a62 (patch)
tree823dd1444789ef1ebff521cc7d6ca36e1c4f740a /apps/plugins
parenta9a891b47b828c20b5b3d3e29983e66d48419377 (diff)
downloadrockbox-5c701b02809ffeec2e9de50e89995dbb85cd7a62.tar.gz
rockbox-5c701b02809ffeec2e9de50e89995dbb85cd7a62.zip
Add support for Windows shortcuts (*.lnk files)
Supports only relative links across the same volume. Change-Id: I4f61bb9d5f2385d5b15d2b9d9a3f814a7ac85b54
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
shopper.c
resistor.c
otp.c
+windows_lnk.c
#ifdef USB_ENABLE_HID
remote_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,-
z7,viewers/frotz,-
z8,viewers/frotz,-
shopper,viewers/shopper,1
+lnk,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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2017 Sebastian Leonhardt
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include "plugin.h"
+
+/**
+ * Follow Windows shortcuts (*.lnk files) in Rockbox.
+ * If the destination is a file, it will be selected in the file browser,
+ * a directory will be entered.
+ * For now, only relative links are supported.
+ */
+
+/* a selection of link flags */
+#define HAS_LINK_TARGET_ID_LIST 0x01
+#define HAS_LINK_INFO 0x02
+#define HAS_NAME 0x04
+#define HAS_RELATIVE_PATH 0x08
+#define HAS_WORKING_DIR 0x10
+#define HAS_ARGUMENTS 0x20
+#define HAS_ICON_LOCATION 0x40
+#define IS_UNICODE 0x80
+#define FORCE_NO_LINK_INFO 0x100
+/* a selection of file attributes flags */
+#define FILE_ATTRIBUTE_DIRECTORY 0x10
+#define FILE_ATTRIBUTE_NORMAL 0x80
+
+
+/**
+ * Read one byte from file
+ * \param fd file descriptor
+ * \param *a where the data should go
+ * \return false if an error occured, true on success
+ */
+static bool read_byte(const int fd, unsigned char *a)
+{
+ if (!rb->read(fd, a, 1))
+ return false;
+ return true;
+}
+
+/**
+ * Read 16-bit word from file, respecting windows endianness (little endian)
+ * \param fd file descriptor
+ * \param *a where the data should go
+ * \return false if an error occured, true on success
+ */
+static bool read_word(const int fd, int *a)
+{
+ unsigned char a1,a2;
+ int r;
+
+ r = read_byte(fd, &a1);
+ if (!r)
+ return false;
+ r = read_byte(fd, &a2);
+ if (!r)
+ return false;
+ *a = (a2<<8) + a1;
+ return true;
+}
+
+/**
+ * Read 32-bit word from file, respecting windows endianness (little endian)
+ * \param fd file descriptor
+ * \param *a where the data should go
+ * \return false if an error occured, true on success
+ */
+static bool read_lword(const int fd, uint32_t *a)
+{
+ int a1,a2;
+ int r;
+
+ r = read_word(fd, &a1);
+ if (!r)
+ return false;
+ r = read_word(fd, &a2);
+ if (!r)
+ return false;
+ *a = (a2<<16) + a1;
+ return true;
+}
+
+
+/**
+ * Scan *.lnk file for relative link target
+ * \param fd file descriptor
+ * \param link_target the extracted link destination
+ * \param target_size available space for the extracted link (in bytes)
+ * \param link_flags the link flags are stored here
+ * \param file_atts file attributes are stored here
+ * \return returns false if extraction failed.
+ */
+static bool extract_link_destination(const int fd,
+ char *link_target, const int target_size,
+ uint32_t *link_flags, uint32_t *file_atts)
+{
+ int r;
+
+ /* Read ShellLinkHeader */
+ uint32_t size;
+ r = read_lword(fd, &size);
+ if (!r) return false;
+ if (size!=0x4c) { /* header size MUST be 76 bytes */
+ DEBUGF("unexpected header size 0x%08lx (must be 0x0000004c)\n", size);
+ return false;
+ }
+
+ /* Skip LinkCLSID (class identifier) */
+ rb->lseek(fd, 0x10, SEEK_CUR);
+ /* We need the LinkFlags and File attribute (to see if target is a directory) */
+ r = read_lword(fd, link_flags);
+ if (!r) return false;
+ r = read_lword(fd, file_atts);
+ if (!r) return false;
+ rb->lseek(fd, size, SEEK_SET); /* Skip to end of header */
+
+ /* For now we only support relative links, so we can exit right away
+ if no relative link structure is present */
+ if (!(*link_flags & HAS_RELATIVE_PATH)) {
+ DEBUGF("Link doesn't have relative path information\n");
+ return false;
+ }
+
+ /* Read (skip) LinkTargetIDList structure if present */
+ if (*link_flags & HAS_LINK_TARGET_ID_LIST) {
+ int size;
+ if (!read_word(fd, &size))
+ return false;
+ rb->lseek(fd, size, SEEK_CUR);
+ }
+
+ /* Read (skip) LinkInfo structure if present */
+ if (*link_flags & HAS_LINK_INFO) {
+ uint32_t size;
+ r = read_lword(fd, &size);
+ if (!r) return false;
+ rb->lseek(fd, size-4, SEEK_CUR);
+ }
+
+ /* String Data section */
+
+ /* Read (skip) NAME_STRING StringData structure if present */
+ if (*link_flags & HAS_NAME) {
+ int ccount;
+ if (!read_word(fd, &ccount))
+ return false;
+ if (*link_flags & IS_UNICODE)
+ rb->lseek(fd, ccount*2, SEEK_CUR);
+ else
+ rb->lseek(fd, ccount, SEEK_CUR);
+ }
+
+ /* Read RELATIVE_PATH StringData structure if present */
+ /* This is finally the data we are searching for! */
+ if (*link_flags & HAS_RELATIVE_PATH) {
+ int ccount;
+ r = read_word(fd, &ccount);
+ if (*link_flags & IS_UNICODE) {
+ unsigned char utf16[4], utf8[10];
+ link_target[0] = '\0';
+ for (int i=0; i<ccount; ++i) {
+ r = read_byte(fd, &utf16[0]);
+ if (!r) return false;
+ r = read_byte(fd, &utf16[1]);
+ if (!r) return false;
+ /* check for surrogate pair and read the second char */
+ if (utf16[1] >= 0xD8 && utf16[1] < 0xE0) {
+ r = read_byte(fd, &utf16[2]);
+ if (!r) return false;
+ r = read_byte(fd, &utf16[3]);
+ if (!r) return false;
+ ++i;
+ }
+ char *ptr = rb->utf16LEdecode(utf16, utf8, 1);
+ *ptr = '\0';
+ rb->strlcat(link_target, utf8, target_size);
+ }
+ }
+ else { /* non-unicode */
+ if (ccount >= target_size) {
+ DEBUGF("ERROR: link target filename exceeds size!");
+ return false;
+ }
+ rb->read(fd, link_target, ccount);
+ link_target[ccount] = '\0';
+ }
+ }
+
+ /* convert from windows to unix subdir separators */
+ for (int i=0; link_target[i] != '\0'; ++i) {
+ if (link_target[i]=='\\')
+ link_target[i] = '/';
+ }
+
+ return true;
+}
+
+
+/**
+ * strip rightmost part of file/pathname to next '/', i.e. remove filename
+ * or last subdirectory. Leaves a trailing '/' character.
+ * \param pathname full path or filename
+*/
+static void strip_rightmost_part(char *pathname)
+{
+ for (int i = rb->strlen(pathname)-2; i >= 0; --i) {
+ if (pathname[i] == '/') {
+ pathname[i+1] = '\0'; /* cut off */
+ return;
+ }
+ }
+ pathname[0] = '\0';
+}
+
+
+/**
+ * Combine link file's absolute path with relative link target to form
+ * (absolute) link destination
+ * \param abs_path full shortcut filename (including path)
+ * \param rel_path the extracted relative link target
+ * \param max_len maximum lengt of combined filename
+ */
+static void assemble_link_dest(char *const abs_path, char const *rel_path,
+ const size_t max_len)
+{
+ strip_rightmost_part(abs_path); /* cut off link filename */
+
+ for (;;) {
+ if (rb->strncmp(rel_path, "../", 3)==0) {
+ rel_path += 3;
+ strip_rightmost_part(abs_path);
+ }
+ else if (rb->strncmp(rel_path, "./", 2)==0) {
+ rel_path += 2;
+ }
+ else
+ break;
+ }
+
+ if (*rel_path=='/')
+ ++rel_path; /* avoid double '/' chars when concatenating */
+ rb->strlcat(abs_path, rel_path, max_len);
+}
+
+
+/**
+ * Select the chosen file in the file browser. A directory (filename ending
+ * with '/') will be entered.
+ * \param link_target link target to be selected in the browser
+ * \return returns false if the target doesn't exist
+ */
+static bool goto_entry(char *link_target)
+{
+ DEBUGF("Trying to go to '%s'...\n", link_target);
+ if (!rb->file_exists(link_target))
+ return false;
+
+ /* Set the browsers dirfilter to the global setting.
+ * This is required in case the plugin was launched
+ * from the plugins browser, in which case the
+ * dirfilter is set to only display .rock files */
+ rb->set_dirfilter(rb->global_settings->dirfilter);
+
+ /* Change directory to the entry selected by the user */
+ rb->set_current_file(link_target);
+ return true;
+}
+
+
+enum plugin_status plugin_start(const void* void_parameter)
+{
+ char *link_filename;
+ char extracted_link[MAX_PATH];
+ char link_target[MAX_PATH];
+ uint32_t lnk_flags;
+ uint32_t file_atts;
+
+ /* This is a viewer, so a parameter must have been specified */
+ if (void_parameter == NULL) {
+ rb->splash(HZ*3, "No *.lnk file selected");
+ return PLUGIN_OK;
+ }
+
+ link_filename = (char*)void_parameter;
+ DEBUGF("Shortcut filename: \"%s\"\n", link_filename);
+
+ int fd = rb->open(link_filename, O_RDONLY);
+ if (fd < 0) {
+ DEBUGF("Can't open link file\n");
+ rb->splashf(HZ*3, "Can't open link file!");
+ return PLUGIN_OK;
+ }
+
+ if (!extract_link_destination(fd, extracted_link, sizeof(extracted_link),
+ &lnk_flags, &file_atts)) {
+ rb->close(fd);
+ DEBUGF("Error in extract_link_destination()\n");
+ rb->splashf(HZ*3, "Unsupported or erroneous link file format");
+ return PLUGIN_OK;
+ }
+ rb->close(fd);
+ DEBUGF("Shortcut destination: \"%s\"\n", extracted_link);
+
+ rb->strcpy(link_target, link_filename);
+ assemble_link_dest(link_target, extracted_link, sizeof(link_target));
+ DEBUGF("Link absolute path: \"%s\"\n", link_target);
+
+ /* if target is a directory, add '/' to the dir name,
+ so that the directory gets entered instead of just highlighted */
+ if (file_atts & FILE_ATTRIBUTE_DIRECTORY)
+ if (link_target[rb->strlen(link_target)-1] != '/')
+ rb->strlcat(link_target, "/", sizeof(link_target));
+
+ if (!goto_entry(link_target)) {
+ char *what;
+ if (file_atts & FILE_ATTRIBUTE_DIRECTORY)
+ what = "directory";
+ else
+ what = "file";
+ rb->splashf(HZ*3, "Can't find %s %s", what, link_target);
+ DEBUGF("Can't find %s %s", what, link_target);
+ return PLUGIN_OK;
+ }
+
+ return PLUGIN_OK;
+}