summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2023-10-11 22:26:36 +0100
committerGitHub <noreply@github.com>2023-10-11 23:26:36 +0200
commitc41b3a1a647155ab9c727dab168baf08e7609372 (patch)
treed34fe9b53dd5d7b71846967b0fc1af166bec2ded
parent33b601e57404fcef058a992ba7767f2c4f16a81b (diff)
Fixes fl_filename_relative on Linux, Mac, and Windows (#787)
* fixed filename_relative for Linux * Fixing fl_filename_relative for MSWindows. * Update documentation * Fixed docs. * Fixes Linux and macOS builds
-rw-r--r--FL/filename.H4
-rw-r--r--src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx143
-rw-r--r--src/filename_absolute.cxx171
3 files changed, 185 insertions, 133 deletions
diff --git a/FL/filename.H b/FL/filename.H
index be7e90ead..b5b76f92f 100644
--- a/FL/filename.H
+++ b/FL/filename.H
@@ -62,8 +62,10 @@ FL_EXPORT Fl_String fl_filename_path(const Fl_String &filename);
FL_EXPORT Fl_String fl_filename_ext(const Fl_String &filename);
FL_EXPORT Fl_String fl_filename_setext(const Fl_String &filename, const Fl_String &new_extension);
FL_EXPORT Fl_String fl_filename_expand(const Fl_String &from);
+FL_EXPORT int fl_filename_absolute(char *to, int tolen, const char *from, const char *cwd);
FL_EXPORT Fl_String fl_filename_absolute(const Fl_String &from);
FL_EXPORT Fl_String fl_filename_absolute(const Fl_String &from, const Fl_String &base);
+FL_EXPORT int fl_filename_relative(char *to, int tolen, const char *from, const char *cwd);
FL_EXPORT Fl_String fl_filename_relative(const Fl_String &from);
FL_EXPORT Fl_String fl_filename_relative(const Fl_String &from, const Fl_String &base);
FL_EXPORT Fl_String fl_getcwd();
@@ -77,9 +79,7 @@ FL_EXPORT Fl_String fl_getcwd();
inline char *fl_filename_setext(char *to, const char *ext) { return fl_filename_setext(to, FL_PATH_MAX, ext); }
inline int fl_filename_expand(char *to, const char *from) { return fl_filename_expand(to, FL_PATH_MAX, from); }
-FL_EXPORT int fl_filename_absolute(char *to, int tolen, const char *from, const char *cwd);
inline int fl_filename_absolute(char *to, const char *from) { return fl_filename_absolute(to, FL_PATH_MAX, from); }
-FL_EXPORT int fl_filename_relative(char *to, int tolen, const char *from, const char *cwd);
inline int fl_filename_relative(char *to, const char *from) { return fl_filename_relative(to, FL_PATH_MAX, from); }
# endif /* __cplusplus */
diff --git a/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx b/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx
index c2d41f98e..faf89e979 100644
--- a/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx
+++ b/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx
@@ -549,90 +549,101 @@ int Fl_WinAPI_System_Driver::filename_expand(char *to, int tolen, const char *fr
int // O - 0 if no change, 1 if changed
Fl_WinAPI_System_Driver::filename_relative(char *to, // O - Relative filename
- int tolen, // I - Size of "to" buffer
- const char *from, // I - Absolute filename
- const char *base) // I - Find path relative to this path
+ int tolen, // I - Size of "to" buffer
+ const char *dest_dir, // I - Absolute filename
+ const char *base_dir) // I - Find path relative to this path
{
- char *newslash; // Directory separator
- const char *slash; // Directory separator
- char *cwd = 0L, *cwd_buf = 0L;
- if (base) cwd = cwd_buf = fl_strdup(base);
-
- // return if "from" is not an absolute path
- if (from[0] == '\0' ||
- (!isdirsep(*from) && !isalpha(*from) && from[1] != ':' &&
- !isdirsep(from[2]))) {
- strlcpy(to, from, tolen);
- if (cwd_buf) free(cwd_buf);
- return 0;
- }
-
- // return if "cwd" is not an absolute path
- if (!cwd || cwd[0] == '\0' ||
- (!isdirsep(*cwd) && !isalpha(*cwd) && cwd[1] != ':' &&
- !isdirsep(cwd[2]))) {
- strlcpy(to, from, tolen);
- if (cwd_buf) free(cwd_buf);
- return 0;
- }
+ // Find the relative path from base_dir to dest_dir.
+ // Both paths must be absolute and well formed (contain no /../ and /./ segments).
- // convert all backslashes into forward slashes
- for (newslash = strchr(cwd, '\\'); newslash; newslash = strchr(newslash + 1, '\\'))
- *newslash = '/';
+ // return if any of the pointers is NULL
+ if (!to || !dest_dir || !base_dir) {
+ return 0;
+ }
- // test for the exact same string and return "." if so
- if (!strcasecmp(from, cwd)) {
- strlcpy(to, ".", tolen);
- free(cwd_buf);
- return (1);
+ // if there is a drive letter, make sure both paths use the same drive
+ if ( base_dir[0] < 128 && isalpha(base_dir[0]) && base_dir[1] == ':'
+ && dest_dir[0] < 128 && isalpha(dest_dir[0]) && dest_dir[1] == ':') {
+ if (tolower(base_dir[0]) != tolower(dest_dir[0])) {
+ strlcpy(to, dest_dir, tolen);
+ return 0;
+ }
+ // same drive, so skip to the start of the path
+ base_dir += 2;
+ dest_dir += 2;
}
- // test for the same drive. Return the absolute path if not
- if (tolower(*from & 255) != tolower(*cwd & 255)) {
- // Not the same drive...
- strlcpy(to, from, tolen);
- free(cwd_buf);
+ // return if `base_dir` or `dest_dir` is not an absolute path
+ if (!isdirsep(*base_dir) || !isdirsep(*dest_dir)) {
+ strlcpy(to, dest_dir, tolen);
return 0;
}
- // compare the path name without the drive prefix
- from += 2; cwd += 2;
+ const char *base_i = base_dir; // iterator through the base directory string
+ const char *base_s = base_dir; // pointer to the last dir separator found
+ const char *dest_i = dest_dir; // iterator through the destination directory
+ const char *dest_s = dest_dir; // pointer to the last dir separator found
// compare both path names until we find a difference
- for (slash = from, newslash = cwd;
- *slash != '\0' && *newslash != '\0';
- slash ++, newslash ++)
- if (isdirsep(*slash) && isdirsep(*newslash)) continue;
- else if (tolower(*slash & 255) != tolower(*newslash & 255)) break;
-
- // skip over trailing slashes
- if ( *newslash == '\0' && *slash != '\0' && !isdirsep(*slash)
- &&(newslash==cwd || !isdirsep(newslash[-1])) )
- newslash--;
-
- // now go back to the first character of the first differing paths segment
- while (!isdirsep(*slash) && slash > from) slash --;
- if (isdirsep(*slash)) slash ++;
-
- // do the same for the current dir
- if (isdirsep(*newslash)) newslash --;
- if (*newslash != '\0')
- while (!isdirsep(*newslash) && newslash > cwd) newslash --;
+ for (;;) {
+#if 0 // case sensitive
+ base_i++;
+ dest_i++;
+ char b = *base_i, d = *dest_i;
+#else // case insensitive
+ base_i += fl_utf8len1(*base_i);
+ int b = fl_tolower(fl_utf8decode(base_i, NULL, NULL));
+ dest_i += fl_utf8len1(*dest_i);
+ int d = fl_tolower(fl_utf8decode(dest_i, NULL, NULL));
+#endif
+ int b0 = (b == 0) || (isdirsep(b));
+ int d0 = (d == 0) || (isdirsep(d));
+ if (b0 && d0) {
+ base_s = base_i;
+ dest_s = dest_i;
+ }
+ if (b == 0 || d == 0)
+ break;
+ if (b != d)
+ break;
+ }
+ // base_s and dest_s point at the last separator we found
+ // base_i and dest_i point at the first character that differs
+
+ // test for the exact same string and return "." if so
+ if ( (base_i[0] == 0 || (isdirsep(base_i[0]) && base_i[1] == 0))
+ && (dest_i[0] == 0 || (isdirsep(dest_i[0]) && dest_i[1] == 0))) {
+ strlcpy(to, ".", tolen);
+ return 0;
+ }
// prepare the destination buffer
- to[0] = '\0';
+ to[0] = '\0';
to[tolen - 1] = '\0';
- // now add a "previous dir" sequence for every following slash in the cwd
- while (*newslash != '\0') {
- if (isdirsep(*newslash)) strlcat(to, "../", tolen);
- newslash ++;
+ // count the directory segments remaining in `base_dir`
+ int n_up = 0;
+ for (;;) {
+ char b = *base_s++;
+ if (b == 0)
+ break;
+ if (isdirsep(b) && *base_s)
+ n_up++;
}
+ // now add a "previous dir" sequence for every following slash in the cwd
+ if (n_up > 0)
+ strlcat(to, "..", tolen);
+ for (; n_up > 1; --n_up)
+ strlcat(to, "/..", tolen);
+
// finally add the differing path from "from"
- strlcat(to, slash, tolen);
+ if (*dest_s) {
+ if (n_up)
+ strlcat(to, "/", tolen);
+ strlcat(to, dest_s + 1, tolen);
+ }
- free(cwd_buf);
return 1;
}
diff --git a/src/filename_absolute.cxx b/src/filename_absolute.cxx
index 0d0155df1..46c44bfba 100644
--- a/src/filename_absolute.cxx
+++ b/src/filename_absolute.cxx
@@ -28,7 +28,7 @@
#include <stdlib.h>
#include "flstring.h"
-static inline int isdirsep(char c) {return c == '/';}
+static inline int isdirsep(int c) {return c == '/';}
/** Makes a filename absolute from a relative filename to the current working directory.
\code
@@ -124,22 +124,49 @@ int Fl_System_Driver::filename_absolute(char *to, int tolen, const char *from, c
/** Makes a filename relative to the current working directory.
- \code
- #include <FL/filename.H>
- [..]
- fl_chdir("/var/tmp/somedir"); // set cwd to /var/tmp/somedir
- [..]
- char out[FL_PATH_MAX];
- fl_filename_relative(out, sizeof(out), "/var/tmp/somedir/foo.txt"); // out="foo.txt", return=1
- fl_filename_relative(out, sizeof(out), "/var/tmp/foo.txt"); // out="../foo.txt", return=1
- fl_filename_relative(out, sizeof(out), "foo.txt"); // out="foo.txt", return=0 (no change)
- fl_filename_relative(out, sizeof(out), "./foo.txt"); // out="./foo.txt", return=0 (no change)
- fl_filename_relative(out, sizeof(out), "../foo.txt"); // out="../foo.txt", return=0 (no change)
- \endcode
- \param[out] to resulting relative filename
- \param[in] tolen size of the relative filename buffer
- \param[in] from absolute filename
- \return 0 if no change, non zero otherwise
+
+ Return the \a from path made relative to the working directory, similar to
+ C++17 `std::filesystem::path::lexically_relative`. This function can also be
+ called with a fourth argument for a user supplied \a base directory path
+
+ These conversions are purely lexical. They do not check that the paths exist,
+ do not follow symlinks, and do not access the filesystem at all.
+
+ Path arguments must be absolute (start at the root directory) and must not
+ contain `.` or `..` segments, or double separators. A single trailing
+ separator is ok.
+
+ On Windows, path arguments must start with a drive name, e.g. `c:\`.
+ Windows network paths and other special paths starting
+ with a double separator are not supported (`\\cloud\drive\path`,
+ `\\?\`, etc.) . Separators can be `\` and `/` and will be preserved.
+ Newly created separators are alway the forward slash `/`.
+
+ On Windows and macOS, the path segment tests are case insensitive.
+
+ If the path can not be generated, \a from path is copied into the \a to
+ buffer and 0 is returned.
+
+ \code
+ #include <FL/filename.H>
+ [..]
+ fl_chdir("/var/tmp/somedir"); // set cwd to /var/tmp/somedir
+ [..]
+ char out[FL_PATH_MAX];
+ fl_filename_relative(out, sizeof(out), "/var/tmp/somedir/foo.txt"); // out="foo.txt", return=1
+ fl_filename_relative(out, sizeof(out), "/var/tmp/foo.txt"); // out="../foo.txt", return=1
+ fl_filename_relative(out, sizeof(out), "foo.txt"); // out="foo.txt", return=0 (no change)
+ fl_filename_relative(out, sizeof(out), "./foo.txt"); // out="./foo.txt", return=0 (no change)
+ fl_filename_relative(out, sizeof(out), "../foo.txt"); // out="../foo.txt", return=0 (no change)
+ \endcode
+
+ \param[out] to resulting relative filename
+ \param[in] tolen size of the relative filename buffer
+ \param[in] from absolute filename
+ \return 0 if no change, non zero otherwise
+ \see fl_filename_relative(char *to, int tolen, const char *from, const char *base)
+ \see fl_filename_relative(const Fl_String &from, const Fl_String &base)
+ \see fl_filename_relative(const Fl_String &from)
*/
int fl_filename_relative(char *to, int tolen, const char *from)
{
@@ -154,11 +181,13 @@ int fl_filename_relative(char *to, int tolen, const char *from)
/** Makes a filename relative to any other directory.
- \param[out] to resulting relative filename
+
+ \param[out] to resulting relative filepath
\param[in] tolen size of the relative filename buffer
- \param[in] from absolute filename
- \param[in] base generate filename relative to this absolute filename
+ \param[in] from absolute filepath
+ \param[in] base generate filepath relative to this absolute filepath
\return 0 if no change, non zero otherwise
+ \see fl_filename_relative(char *to, int tolen, const char *from)
*/
int fl_filename_relative(char *to, int tolen, const char *from, const char *base) {
return Fl::system_driver()->filename_relative(to, tolen, from, base);
@@ -171,70 +200,82 @@ int fl_filename_relative(char *to, int tolen, const char *from, const char *base
\{
*/
-int Fl_System_Driver::filename_relative(char *to, int tolen, const char *from, const char *base)
+int Fl_System_Driver::filename_relative(char *to, int tolen, const char *dest_dir, const char *base_dir)
{
- char *newslash; // Directory separator
- const char *slash; // Directory separator
- char *cwd = 0L, *cwd_buf = 0L;
- if (base) cwd = cwd_buf = fl_strdup(base);
-
- // return if "from" is not an absolute path
- if (from[0] == '\0' || !isdirsep(*from)) {
- strlcpy(to, from, tolen);
- if (cwd_buf) free(cwd_buf);
+ // Find the relative path from base_dir to dest_dir.
+ // Both paths must be absolute and well formed (contain no /../ and /./ segments).
+ const char *base_i = base_dir; // iterator through the base directory string
+ const char *base_s = base_dir; // pointer to the last dir separator found
+ const char *dest_i = dest_dir; // iterator through the destination directory
+ const char *dest_s = dest_dir; // pointer to the last dir separator found
+
+ // return if any of the pointers is NULL
+ if (!to || !dest_dir || !base_dir) {
return 0;
}
- // return if "cwd" is not an absolute path
- if (!cwd || cwd[0] == '\0' || !isdirsep(*cwd)) {
- strlcpy(to, from, tolen);
- if (cwd_buf) free(cwd_buf);
+ // return if `base_dir` or `dest_dir` is not an absolute path
+ if (!isdirsep(*base_dir) || !isdirsep(*dest_dir)) {
+ strlcpy(to, dest_dir, tolen);
return 0;
}
+ // compare both path names until we find a difference
+ for (;;) {
+#ifndef __APPLE__ // case sensitive
+ base_i++;
+ dest_i++;
+ char b = *base_i, d = *dest_i;
+#else // case insensitive
+ base_i += fl_utf8len1(*base_i);
+ int b = fl_tolower(fl_utf8decode(base_i, NULL, NULL));
+ dest_i += fl_utf8len1(*dest_i);
+ int d = fl_tolower(fl_utf8decode(dest_i, NULL, NULL));
+#endif
+ int b0 = (b==0) || (isdirsep(b));
+ int d0 = (d==0) || (isdirsep(d));
+ if (b0 && d0) {
+ base_s = base_i;
+ dest_s = dest_i;
+ }
+ if (b==0 || d==0) break;
+ if (b!=d) break;
+ }
+ // base_s and dest_s point at the last separator we found
+ // base_i and dest_i point at the first character that differs
+
// test for the exact same string and return "." if so
- if (!strcmp(from, cwd)) {
+ if ( (base_i[0] == 0 || (isdirsep(base_i[0]) && base_i[1] == 0))
+ && (dest_i[0] == 0 || (isdirsep(dest_i[0]) && dest_i[1] == 0))) {
strlcpy(to, ".", tolen);
- free(cwd_buf);
- return (1);
+ return 0;
}
- // compare both path names until we find a difference
- for (slash = from, newslash = cwd;
- *slash != '\0' && *newslash != '\0';
- slash ++, newslash ++)
- if (isdirsep(*slash) && isdirsep(*newslash)) continue;
- else if (*slash != *newslash) break;
-
- // skip over trailing slashes
- if ( *newslash == '\0' && *slash != '\0' && !isdirsep(*slash)
- &&(newslash==cwd || !isdirsep(newslash[-1])) )
- newslash--;
-
- // now go back to the first character of the first differing paths segment
- while (!isdirsep(*slash) && slash > from) slash --;
- if (isdirsep(*slash)) slash ++;
-
- // do the same for the current dir
- if (isdirsep(*newslash)) newslash --;
- if (*newslash != '\0')
- while (!isdirsep(*newslash) && newslash > cwd) newslash --;
-
// prepare the destination buffer
to[0] = '\0';
to[tolen - 1] = '\0';
- // now add a "previous dir" sequence for every following slash in the cwd
- while (*newslash != '\0') {
- if (isdirsep(*newslash)) strlcat(to, "../", tolen);
-
- newslash ++;
+ // count the directory segments remaining in `base_dir`
+ int n_up = 0;
+ for (;;) {
+ char b = *base_s++;
+ if (b==0) break;
+ if (isdirsep(b) && *base_s) n_up++;
}
+ // now add a "previous dir" sequence for every following slash in the cwd
+ if (n_up>0)
+ strlcat(to, "..", tolen);
+ for (; n_up>1; --n_up)
+ strlcat(to, "/..", tolen);
+
// finally add the differing path from "from"
- strlcat(to, slash, tolen);
+ if (*dest_s) {
+ if (n_up)
+ strlcat(to, "/", tolen);
+ strlcat(to, dest_s+1, tolen);
+ }
- free(cwd_buf);
return 1;
}