#ifndef MONGOCRYPT_PATH_PRIVATE_H #define MONGOCRYPT_PATH_PRIVATE_H #include "./user-check.h" #include "./str.h" #include #ifndef _WIN32 #include #else #include "./windows-lean.h" #endif typedef enum mpath_format { /// The POSIX path format MPATH_POSIX = 'p', /// The Win32 path format MPATH_WIN32 = 'w', /// The native path format for the current platform MPATH_NATIVE = #ifdef _WIN32 MPATH_WIN32, #else MPATH_POSIX, #endif } mpath_format; /** * @brief Determine if the given character is a path separator for the given * path format * * @param c A path character * @param f The path format to use */ static inline bool mpath_is_sep (char c, mpath_format f) { if (f == MPATH_WIN32) { return c == '/' || c == '\\'; } else { return c == '/'; } } /** * @brief Obtain the preferred path separator character for the given format */ static inline char mpath_preferred_sep (mpath_format f) { if (f == MPATH_WIN32) { return '\\'; } else { return '/'; } } /** * @brief Obtain the path string denoting the application's current working * directory * * @return mstr A new string which must be freed with mstr_free() */ static inline mstr mpath_current_path (void) { #if _WIN32 while (1) { DWORD len = GetCurrentDirectoryW (0, NULL); wchar_t *wstr = calloc (sizeof (wchar_t), len); DWORD got_len = GetCurrentDirectoryW (len, wstr); if (got_len > len) { free (wstr); continue; } mstr_narrow_result nar = mstr_win32_narrow (wstr); free (wstr); assert (nar.error == 0); return nar.string; } #else mstr_mut mut = mstr_new (8096); char *p = getcwd (mut.data, mut.len); if (p == NULL) { mstr_free (mut.mstr); return MSTR_NULL; } mstr ret = mstr_copy_cstr (mut.data); mstr_free (mut.mstr); return ret; #endif } /** * @brief Determine whether the given path string has a trailing path separator */ static inline bool mpath_has_trailing_sep (mstr_view path, mpath_format f) { return path.len && mpath_is_sep (path.data[path.len - 1], f); } /** * @brief Obtain the parent path of the given path. */ static inline mstr_view mpath_parent (mstr_view path, mpath_format f) { if (mpath_has_trailing_sep (path, f)) { // Remove trailing separators: while (mpath_has_trailing_sep (path, f)) { path = mstrv_remove_suffix (path, 1); } return path; } // Remove everything that isn't a path separator: while (path.len != 0 && !mpath_has_trailing_sep (path, f)) { path = mstrv_remove_suffix (path, 1); } // Remove trailing separators again while (path.len > 1 && mpath_has_trailing_sep (path, f)) { path = mstrv_remove_suffix (path, 1); } // The result is the parent path. return path; } /** * @brief Obtain the filename denoted by the given path. * * The returned path will include no directory separators. If the given path * ends with a directory separator, the single-dot '.' path is returned instead. */ static inline mstr_view mpath_filename (mstr_view path, mpath_format f) { if (!path.len) { return path; } const char *it = path.data + path.len; while (it != path.data && !mpath_is_sep (it[-1], f)) { --it; } size_t off = (size_t) (it - path.data); mstr_view fname = mstrv_subview (path, off, path.len); if (fname.len == 0) { return mstrv_lit ("."); } return fname; } /** * @brief Join the two given paths into a single path * * The two strings will be combined into a single string with a path separator * between them. If either string is empty, the other string will be copied * without modification. * * @param base The left-hand of the join * @param suffix The right-hand of the join * @param f The path format to use * @return mstr A new string resulting from the join */ static inline mstr mpath_join (mstr_view base, mstr_view suffix, mpath_format f) { if (!base.len) { return mstr_copy (suffix); } if (mpath_has_trailing_sep (base, f)) { return mstr_append (base, suffix); } if (!suffix.len) { return mstr_copy (base); } if (mpath_is_sep (suffix.data[0], f)) { return mstr_append (base, suffix); } // We must insert a path separator between the two strings assert (base.len <= SIZE_MAX - suffix.len - 1u); mstr_mut r = mstr_new (base.len + suffix.len + 1); char *p = r.data; memcpy (p, base.data, base.len); p += base.len; *p++ = mpath_preferred_sep (f); memcpy (p, suffix.data, suffix.len); return r.mstr; } /** * @brief Obtain the root name for the given path. * * For the Windows format, this will return the drive letter, if present. * Otherwise, this will return an empty string. */ static inline mstr_view mpath_root_name (mstr_view path, mpath_format f) { if (f == MPATH_WIN32 && path.len > 1) { char c = path.data[0]; if (path.len > 2 && path.data[1] == ':' && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) { return mstrv_subview (path, 0, 2); } } return mstrv_subview (path, 0, 0); } /** * @brief Return the root directory of the given path, if present. * * @note This will not include the drive letter of a Win32 path. */ static inline mstr_view mpath_root_directory (mstr_view path, mpath_format f) { mstr_view rname = mpath_root_name (path, f); path = mstrv_subview (path, rname.len, path.len); if (path.len && mpath_is_sep (path.data[0], f)) { return mstrv_subview (path, 0, 1); } else { return mstrv_subview (path, 0, 0); } } /** * @brief Obtain the root filepath of the given path. * * This will include both the root name and the root filepath, if present. */ static inline mstr_view mpath_root_path (mstr_view path, mpath_format f) { mstr_view rname = mpath_root_name (path, f); mstr_view rdir = mpath_root_directory (path, f); assert (rname.len <= SIZE_MAX - rdir.len); return mstrv_subview (path, 0, rname.len + rdir.len); } /** * @brief Determine whether the given filepath designates a single unambiguous * filesystem location. * * @note A Win32 filepath without a drive letter is not absolute! */ static inline bool mpath_is_absolute (mstr_view path, mpath_format f) { if (f == MPATH_WIN32) { // Win32 requires both a root name and a root directory for an absolute // path return mpath_root_name (path, f).len && mpath_root_directory (path, f).len; } else { // POSIX doesn't have "root names" return mpath_root_directory (path, f).len; } } /** * @brief Obtain a relative path from the given filepath * * If the path has a root path, returns the content of the path following that * root path, otherwise returns the same path itself. */ static inline mstr_view mpath_relative_path (mstr_view path, mpath_format f) { mstr_view root = mpath_root_path (path, f); return mstrv_subview (path, root.len, path.len); } /** * @brief Convert the filepath from one format to a preferred form in another * format. * * @note The return value must be freed with mstr_free() */ static inline mstr mpath_to_format (mpath_format from, mstr_view path, mpath_format to) { mstr_mut ret = mstr_new (path.len); const char *p = path.data; char *out = ret.data; const char *stop = path.data + path.len; for (; p != stop; ++p, ++out) { if (mpath_is_sep (*p, from)) { *out = mpath_preferred_sep (to); } else { *out = *p; } } return ret.mstr; } /** * @brief Determine whether the given path is relative (not absolute) */ static inline bool mpath_is_relative (mstr_view path, mpath_format f) { return !mpath_is_absolute (path, f); } /** * @brief Convert the given path to an absolute path, if it is not already. * * @note The return value must be freed with mstr_free() */ static inline mstr mpath_absolute (mstr_view path, mpath_format f); /** * @brief Resolve a path to an absolute path from the given base path. * * @note This is not the same as mpath_join(): If the given path is already * absolute, returns that path unchanged. Otherwise, resolves that path as being * relative to `base`. * * @note If `base` is also a relative path, it will also be given to * mpath_absolute() to resolve it. */ static inline mstr mpath_absolute_from (mstr_view path, mstr_view base, mpath_format f) { mstr_view rname = mpath_root_name (path, f); mstr_view rdir = mpath_root_directory (path, f); if (rname.len) { if (rdir.len) { // The path is already fully absolute return mstr_copy (path); } else { mstr abs_base = mpath_absolute (base, f); mstr_view base_rdir = mpath_root_directory (abs_base.view, f); mstr_view base_relpath = mpath_relative_path (abs_base.view, f); mstr_view relpath = mpath_relative_path (path, f); mstr ret = mstr_copy (rname); mstr_assign (&ret, mpath_join (ret.view, base_rdir, f)); mstr_assign (&ret, mpath_join (ret.view, base_relpath, f)); mstr_assign (&ret, mpath_join (ret.view, relpath, f)); mstr_free (abs_base); return ret; } } else { // No root name if (rdir.len) { if (f == MPATH_POSIX) { // No root name, but a root directory on a POSIX path indicates an // absolute path return mstr_copy (path); } mstr abs_base = mpath_absolute (base, f); mstr_view base_rname = mpath_root_name (abs_base.view, f); mstr ret = mpath_join (base_rname, path, f); mstr_free (abs_base); return ret; } else { mstr abs_base = mpath_absolute (base, f); mstr r = mpath_join (abs_base.view, path, f); mstr_free (abs_base); return r; } } } static inline mstr mpath_absolute (mstr_view path, mpath_format f) { if (mpath_is_absolute (path, f)) { return mstr_copy (path); } mstr cur = mpath_current_path (); mstr ret = mpath_absolute_from (path, cur.view, MPATH_NATIVE); mstr_assign (&ret, mpath_to_format (MPATH_NATIVE, ret.view, f)); mstr_free (cur); return ret; } #endif // MONGOCRYPT_PATH_PRIVATE_H