From d697e8a992268228243278cc2b5a8f46b07a9208 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Wed, 10 Jan 2018 18:25:26 +1100 Subject: Convert zippath directory to a C++ interface --- src/lib/util/zippath.cpp | 1374 ++++++++++++++++++++-------------------------- src/lib/util/zippath.h | 42 +- 2 files changed, 616 insertions(+), 800 deletions(-) (limited to 'src/lib') diff --git a/src/lib/util/zippath.cpp b/src/lib/util/zippath.cpp index f85e5c63b71..e04281cd98e 100644 --- a/src/lib/util/zippath.cpp +++ b/src/lib/util/zippath.cpp @@ -22,63 +22,8 @@ namespace util { -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ - -/** - * @class zippath_directory - * - * @brief A zippath directory. - */ - -class zippath_directory -{ -public: - zippath_directory() - : returned_parent(false) - , directory() - , called_zip_first(false) - , zipfile() - , returned_dirlist() - { - } - - /* common */ - /** @brief true to returned parent. */ - bool returned_parent; - /** @brief The returned entry. */ - osd::directory::entry returned_entry; - - /* specific to normal directories */ - /** @brief Pathname of the directory. */ - osd::directory::ptr directory; - - /* specific to ZIP directories */ - /** @brief true to called zip first. */ - bool called_zip_first; - /** @brief The zipfile. */ - archive_file::ptr zipfile; - /** @brief The zipprefix. */ - std::string zipprefix; - /** @brief The returned dirlist. */ - std::forward_list returned_dirlist; -}; - - -/*************************************************************************** - FUNCTION PROTOTYPES -***************************************************************************/ - -static int zippath_find_sub_path(archive_file &zipfile, std::string const &subpath, osd::directory::entry::entry_type &type); -static bool is_zip_file(std::string const &path); -static bool is_zip_file_separator(char c); -static bool is_7z_file(std::string const &path); - -/*************************************************************************** - PATH OPERATIONS -***************************************************************************/ +namespace { /** * @fn int is_path_separator(char c) @@ -94,977 +39,846 @@ static bool is_7z_file(std::string const &path); int is_path_separator(char c) { + // FIXME: don't assume DOS-like behaviour - backslashes are valid filename characters on *NIX return (c == '/') || (c == '\\'); } // ------------------------------------------------- -// parse_parent_path - parses out the parent path +// is_root - tests to see if this path is the root // ------------------------------------------------- -/** - * @fn static void parse_parent_path(const std::string &path, std::string::size_type *beginpos, std::string::size_type *endpos) - * - * @brief Parse parent path. - * - * @param path Full pathname of the file. - * @param [in,out] beginpos If non-null, the beginpos. - * @param [in,out] endpos If non-null, the endpos. - */ - -static void parse_parent_path(const std::string &path, std::string::size_type *beginpos, std::string::size_type *endpos) +bool is_root(std::string const &path) { - std::string::size_type length = path.length(); - std::string::size_type pos; + // FIXME: get rid of the assumption that paths are DOS-like - // skip over trailing path separators - pos = length ? (length - 1) : std::string::npos; - while ((pos > 0) && (pos != std::string::npos) && is_path_separator(path[pos])) - pos--; + std::string::size_type i = 0; - // return endpos - if (endpos != nullptr) - *endpos = pos; + // skip drive letter + if (isalpha(path[i]) && (path[i + 1] == ':')) + i += 2; - // now skip until we find a path separator - while ((pos != std::string::npos) && !is_path_separator(path[pos])) - pos = (pos > 0) ? pos - 1 : std::string::npos; + // skip path separators + while (is_path_separator(path[i])) + i++; - // return beginpos - if (beginpos != nullptr) - *beginpos = pos; + return path[i] == '\0'; } // ------------------------------------------------- -// zippath_parent - retrieves the parent directory +// is_7z_file - tests to see if this file is a +// 7-zip file // ------------------------------------------------- -std::string &zippath_parent(std::string &dst, const std::string &path) +bool is_7z_file(std::string const &path) { - std::string::size_type pos; - parse_parent_path(path, &pos, nullptr); - - if (pos != std::string::npos) - dst = path.substr(0, pos + 1); - else - dst.clear(); - return dst; + auto const s = path.rfind('.'); + return (std::string::npos != s) && !core_stricmp(path.c_str() + s, ".7z"); } - // ------------------------------------------------- -// zippath_parent - retrieves the parent directory +// is_zip_file - tests to see if this file is a +// ZIP file // ------------------------------------------------- -std::string zippath_parent(const std::string &path) +bool is_zip_file(std::string const &path) { - std::string result; - zippath_parent(result, path); - return result; + auto const s = path.rfind('.'); + return (std::string::npos != s) && !core_stricmp(path.c_str() + s, ".zip"); } // ------------------------------------------------- -// zippath_parent_basename - retrieves the parent -// directory basename +// is_zip_file_separator - returns whether this +// character is a path separator within a ZIP file // ------------------------------------------------- -/** - * @fn std::string &zippath_parent_basename(std::string &dst, const std::string &path) - * - * @brief Zippath parent basename. - * - * @param [in,out] dst Destination for the. - * @param path Full pathname of the file. - * - * @return A std::string& - */ - -std::string &zippath_parent_basename(std::string &dst, const std::string &path) +bool is_zip_file_separator(char c) { - std::string::size_type beginpos, endpos; - parse_parent_path(path, &beginpos, &endpos); - dst.copy((char*)(path.c_str() + beginpos + 1), endpos - beginpos); - return dst; + return (c == '/') || (c == '\\'); } // ------------------------------------------------- -// zippath_parent_basename - retrieves the parent -// directory basename +// is_zip_path_separator - returns whether this +// character is a path separator within a ZIP path // ------------------------------------------------- -std::string zippath_parent_basename(const std::string &path) +bool is_zip_path_separator(char c) { - std::string result; - zippath_parent_basename(result, path); - return result; + return is_zip_file_separator(c) || is_path_separator(c); } // ------------------------------------------------- -// zippath_combine - combines two paths +// next_path_char - lexes out the next path +// character, normalizing separators as '/' // ------------------------------------------------- -/** - * @fn std::string &zippath_combine(std::string &dst, const char *path1, const char *path2) - * - * @brief Zippath combine. - * - * @param [in,out] dst Destination for the. - * @param path1 The first path. - * @param path2 The second path. - * - * @return A std::string& - */ - -std::string &zippath_combine(std::string &dst, const std::string &path1, const std::string &path2) +char next_path_char(std::string const &s, std::string::size_type &pos) { - if (path2 == ".") - { - dst.assign(path1); - } - else if (path2 == "..") + // skip over any initial separators + if (pos == 0) { - dst = zippath_parent(path1); + while ((pos < s.length()) && is_zip_file_separator(s[pos])) + pos++; } - else if (osd_is_absolute_path(path2)) + + // are we at a path separator? + if (pos == s.length()) { - dst.assign(path2); + // return NUL + return '\0'; } - else if (!path1.empty() && !is_path_separator(*path1.rbegin())) + else if (is_zip_file_separator(s[pos])) { - dst.assign(path1).append(PATH_SEPARATOR).append(path2); + // skip over path separators + while((pos < s.length()) && is_zip_file_separator(s[pos])) + pos++; + + // normalize as '/' + return '/'; } else { - dst.assign(path1).append(path2); + // return character + return std::tolower(s[pos++]); } - return dst; } + // ------------------------------------------------- -// zippath_combine - combines two paths +// zippath_find_sub_path - attempts to identify the +// type of a sub path in a zip file // ------------------------------------------------- -std::string zippath_combine(const std::string &path1, const std::string &path2) +int zippath_find_sub_path(archive_file &zipfile, std::string const &subpath, osd::directory::entry::entry_type &type) { - std::string result; - zippath_combine(result, path1, path2); - return result; -} + for (int header = zipfile.first_file(); header >= 0; header = zipfile.next_file()) + { + std::string::size_type i = 0, j = 0; + char c1, c2; + do + { + c1 = next_path_char(zipfile.current_name(), i); + c2 = next_path_char(subpath, j); + } + while ((c1 == c2) && c1 && c2); + + if (!c2 || ((c2 == '/') && !(c2 = next_path_char(subpath, j)))) + { + if (!c1) + { + type = zipfile.current_is_directory() ? osd::directory::entry::entry_type::DIR : osd::directory::entry::entry_type::FILE; + return header; + } + else if ((c1 == '/') || (i <= 1U)) + { + type = osd::directory::entry::entry_type::DIR; + return header; + } + } + } + type = osd::directory::entry::entry_type::NONE; + return -1; +} -/*************************************************************************** - FILE OPERATIONS -***************************************************************************/ // ------------------------------------------------- -// file_error_from_zip_error - translates a -// osd_file::error to a zip_error +// parse_parent_path - parses out the parent path // ------------------------------------------------- -/** - * @fn static osd_file::error file_error_from_zip_error(archive_file::error ziperr) - * - * @brief File error from zip error. - * - * @param ziperr The ziperr. - * - * @return A osd_file::error. - */ - -static osd_file::error file_error_from_zip_error(archive_file::error ziperr) +void parse_parent_path(const std::string &path, std::string::size_type *beginpos, std::string::size_type *endpos) { - osd_file::error filerr; - switch(ziperr) - { - case archive_file::error::NONE: - filerr = osd_file::error::NONE; - break; - case archive_file::error::OUT_OF_MEMORY: - filerr = osd_file::error::OUT_OF_MEMORY; - break; - case archive_file::error::BAD_SIGNATURE: - case archive_file::error::DECOMPRESS_ERROR: - case archive_file::error::FILE_TRUNCATED: - case archive_file::error::FILE_CORRUPT: - case archive_file::error::UNSUPPORTED: - case archive_file::error::FILE_ERROR: - filerr = osd_file::error::INVALID_DATA; - break; - case archive_file::error::BUFFER_TOO_SMALL: - default: - filerr = osd_file::error::FAILURE; - break; - } - return filerr; + std::string::size_type length = path.length(); + std::string::size_type pos; + + // skip over trailing path separators + pos = length ? (length - 1) : std::string::npos; + while ((pos > 0) && (pos != std::string::npos) && is_path_separator(path[pos])) + pos--; + + // return endpos + if (endpos != nullptr) + *endpos = pos; + + // now skip until we find a path separator + while ((pos != std::string::npos) && !is_path_separator(path[pos])) + pos = (pos > 0) ? pos - 1 : std::string::npos; + + // return beginpos + if (beginpos != nullptr) + *beginpos = pos; } // ------------------------------------------------- -// create_core_file_from_zip - creates a core_file -// from a zip file entry +// zippath_resolve - separates a ZIP path out into +// true path and ZIP entry components // ------------------------------------------------- -/** - * @fn static osd_file::error create_core_file_from_zip(archive_file *zip, util::core_file::ptr &file) - * - * @brief Creates core file from zip. - * - * @param [in,out] zip If non-null, the zip. - * @param header The header. - * @param [in,out] file [in,out] If non-null, the file. - * - * @return The new core file from zip. - */ - -static osd_file::error create_core_file_from_zip(archive_file &zip, util::core_file::ptr &file) +osd_file::error zippath_resolve(const char *path, osd::directory::entry::entry_type &entry_type, archive_file::ptr &zipfile, std::string &newpath) { - osd_file::error filerr; - archive_file::error ziperr; - void *ptr; + newpath.clear(); - ptr = malloc(zip.current_uncompressed_length()); - if (ptr == nullptr) - { - filerr = osd_file::error::OUT_OF_MEMORY; - goto done; - } + // be conservative + entry_type = osd::directory::entry::entry_type::NONE; + zipfile.reset(); - ziperr = zip.decompress(ptr, zip.current_uncompressed_length()); - if (ziperr != archive_file::error::NONE) + std::string apath(path); + std::string apath_trimmed; + osd::directory::entry::entry_type current_entry_type; + bool went_up = false; + do { - filerr = file_error_from_zip_error(ziperr); - goto done; - } - - filerr = util::core_file::open_ram_copy(ptr, zip.current_uncompressed_length(), OPEN_FLAG_READ, file); - if (filerr != osd_file::error::NONE) - goto done; - -done: - if (ptr != nullptr) - free(ptr); - return filerr; -} - - -// ------------------------------------------------- -// zippath_fopen - opens a zip path file -// ------------------------------------------------- - -/** - * @fn osd_file::error zippath_fopen(const std::string &filename, uint32_t openflags, util::core_file::ptr &file, std::string &revised_path) - * - * @brief Zippath fopen. - * - * @param filename Filename of the file. - * @param openflags The openflags. - * @param [in,out] file [in,out] If non-null, the file. - * @param [in,out] revised_path Full pathname of the revised file. - * - * @return A osd_file::error. - */ - -osd_file::error zippath_fopen(const std::string &filename, uint32_t openflags, util::core_file::ptr &file, std::string &revised_path) -{ - osd_file::error filerr = osd_file::error::NOT_FOUND; - archive_file::error ziperr; - archive_file::ptr zip; - int header; - osd::directory::entry::entry_type entry_type; - int len; + // trim the path of trailing path separators + auto i = apath.length(); + while ((i > 1) && is_path_separator(apath[i - 1])) + i--; + apath.resize(i); + apath_trimmed = apath; - /* first, set up the two types of paths */ - std::string mainpath(filename); - std::string subpath; - file = nullptr; + // stat the path + auto current_entry = osd_stat(apath_trimmed); - /* loop through */ - while((file == nullptr) && (mainpath.length() > 0) - && ((openflags == OPEN_FLAG_READ) || (subpath.length() == 0))) - { - /* is the mainpath a ZIP path? */ - if (is_zip_file(mainpath) || is_7z_file(mainpath)) + // did we find anything? + if (current_entry) { - /* this file might be a zip file - lets take a look */ - ziperr = is_zip_file(mainpath) ? archive_file::open_zip(mainpath, zip) : archive_file::open_7z(mainpath, zip); - if (ziperr == archive_file::error::NONE) - { - /* it is a zip file - error if we're not opening for reading */ - if (openflags != OPEN_FLAG_READ) - { - filerr = osd_file::error::ACCESS_DENIED; - goto done; - } - - if (subpath.length() > 0) - header = zippath_find_sub_path(*zip, subpath, entry_type); - else - header = zip->first_file(); - - if (header < 0) - { - filerr = osd_file::error::NOT_FOUND; - goto done; - } - - /* attempt to read the file */ - filerr = create_core_file_from_zip(*zip, file); - if (filerr != osd_file::error::NONE) - goto done; - - /* update subpath, if appropriate */ - if (subpath.length() == 0) - subpath.assign(zip->current_name()); - - /* we're done */ - goto done; - } + // get the entry type and free the stat entry + current_entry_type = current_entry->type; } - - if (subpath.length() == 0) - filerr = util::core_file::open(filename, openflags, file); else - filerr = osd_file::error::NOT_FOUND; - - /* if we errored, then go up a directory */ - if (filerr != osd_file::error::NONE) { - /* go up a directory */ - auto temp = zippath_parent(mainpath); - - /* append to the sub path */ - if (subpath.length() > 0) - { - std::string temp2; - mainpath = mainpath.substr(temp.length()); - temp2.assign(mainpath).append(PATH_SEPARATOR).append(subpath); - subpath.assign(temp2); - } - else - { - mainpath = mainpath.substr(temp.length()); - subpath.assign(mainpath); - } - /* get the new main path, truncating path separators */ - len = temp.length(); - while (len > 0 && is_zip_file_separator(temp[len - 1])) - len--; - temp = temp.substr(0, len); - mainpath.assign(temp); - } - } - -done: - /* store the revised path */ - revised_path.clear(); - if (filerr == osd_file::error::NONE) - { - /* cannonicalize mainpath */ - std::string alloc_fullpath; - filerr = osd_get_full_path(alloc_fullpath, mainpath); - if (filerr == osd_file::error::NONE) - { - revised_path = alloc_fullpath; - if (subpath.length() > 0) - revised_path.append(PATH_SEPARATOR).append(subpath); + // if we have not found the file or directory, go up + current_entry_type = osd::directory::entry::entry_type::NONE; + went_up = true; + apath = zippath_parent(apath); } } + while ((current_entry_type == osd::directory::entry::entry_type::NONE) && !is_root(apath.c_str())); - return filerr; -} - - -/*************************************************************************** - DIRECTORY OPERATIONS -***************************************************************************/ - -// ------------------------------------------------- -// is_root - tests to see if this path is the root -// ------------------------------------------------- - -/** - * @fn static int is_root(const char *path) - * - * @brief Is root. - * - * @param path Full pathname of the file. - * - * @return An int. - */ + // if we did not find anything, then error out + if (current_entry_type == osd::directory::entry::entry_type::NONE) + return osd_file::error::NOT_FOUND; -static int is_root(const char *path) -{ - int i = 0; + // is this file a ZIP file? + if ((current_entry_type == osd::directory::entry::entry_type::FILE) && + ((is_zip_file(apath_trimmed) && (archive_file::open_zip(apath_trimmed, zipfile) == archive_file::error::NONE)) || + (is_7z_file(apath_trimmed) && (archive_file::open_7z(apath_trimmed, zipfile) == archive_file::error::NONE)))) + { + auto i = strlen(path + apath.length()); + while ((i > 0) && is_zip_path_separator(path[apath.length() + i - 1])) + i--; + newpath.assign(path + apath.length(), i); - /* skip drive letter */ - if (isalpha(path[i]) && (path[i + 1] == ':')) - i += 2; + // this was a true ZIP path - attempt to identify the type of path + zippath_find_sub_path(*zipfile, newpath, current_entry_type); + if (current_entry_type == osd::directory::entry::entry_type::NONE) + return osd_file::error::NOT_FOUND; + } + else + { + // this was a normal path + if (went_up) + return osd_file::error::NOT_FOUND; - /* skip path separators */ - while (is_path_separator(path[i])) - i++; + newpath = path; + } - return path[i] == '\0'; + // success! + entry_type = current_entry_type; + return osd_file::error::NONE; } - // ------------------------------------------------- -// is_7z_file - tests to see if this file is a -// 7-zip file +// file_error_from_zip_error - translates a +// osd_file::error to a zip_error // ------------------------------------------------- -/** - * @fn static int is_7z_file(const char *path) - * - * @brief Is 7z file. - * - * @param path Full pathname of the file. - * - * @return An int. - */ - -static bool is_7z_file(std::string const &path) +osd_file::error file_error_from_zip_error(archive_file::error ziperr) { - auto const s = path.rfind('.'); - return (std::string::npos != s) && !core_stricmp(path.c_str() + s, ".7z"); + osd_file::error filerr; + switch(ziperr) + { + case archive_file::error::NONE: + filerr = osd_file::error::NONE; + break; + case archive_file::error::OUT_OF_MEMORY: + filerr = osd_file::error::OUT_OF_MEMORY; + break; + case archive_file::error::BAD_SIGNATURE: + case archive_file::error::DECOMPRESS_ERROR: + case archive_file::error::FILE_TRUNCATED: + case archive_file::error::FILE_CORRUPT: + case archive_file::error::UNSUPPORTED: + case archive_file::error::FILE_ERROR: + filerr = osd_file::error::INVALID_DATA; + break; + case archive_file::error::BUFFER_TOO_SMALL: + default: + filerr = osd_file::error::FAILURE; + break; + } + return filerr; } // ------------------------------------------------- -// is_zip_file - tests to see if this file is a -// ZIP file +// create_core_file_from_zip - creates a core_file +// from a zip file entry // ------------------------------------------------- -static bool is_zip_file(std::string const &path) +osd_file::error create_core_file_from_zip(archive_file &zip, util::core_file::ptr &file) { - auto const s = path.rfind('.'); - return (std::string::npos != s) && !core_stricmp(path.c_str() + s, ".zip"); -} - + osd_file::error filerr; + archive_file::error ziperr; + void *ptr = malloc(zip.current_uncompressed_length()); + if (!ptr) + { + filerr = osd_file::error::OUT_OF_MEMORY; + goto done; + } -// ------------------------------------------------- -// is_zip_file_separator - returns whether this -// character is a path separator within a ZIP file -// ------------------------------------------------- + ziperr = zip.decompress(ptr, zip.current_uncompressed_length()); + if (ziperr != archive_file::error::NONE) + { + filerr = file_error_from_zip_error(ziperr); + goto done; + } -/** - * @fn static int is_zip_file_separator(char c) - * - * @brief Is zip file separator. - * - * @param c The character. - * - * @return An int. - */ + filerr = util::core_file::open_ram_copy(ptr, zip.current_uncompressed_length(), OPEN_FLAG_READ, file); + if (filerr != osd_file::error::NONE) + goto done; -static bool is_zip_file_separator(char c) -{ - return (c == '/') || (c == '\\'); +done: + if (ptr != nullptr) + free(ptr); + return filerr; } -// ------------------------------------------------- -// is_zip_path_separator - returns whether this -// character is a path separator within a ZIP path -// ------------------------------------------------- - -/** - * @fn static int is_zip_path_separator(char c) - * - * @brief Is zip path separator. - * - * @param c The character. - * - * @return An int. - */ +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ -static bool is_zip_path_separator(char c) +class zippath_directory_base : public zippath_directory { - return is_zip_file_separator(c) || is_path_separator(c); -} +protected: + zippath_directory_base(bool fake_parent) : m_returned_parent(!fake_parent) + { + } + virtual osd::directory::entry const *readdir() override + { + if (!m_returned_parent) + { + // return a fake entry representing the parent before anything else + m_returned_parent = true; + m_synthetic_entry.name = ".."; + m_synthetic_entry.type = osd::directory::entry::entry_type::DIR; + m_synthetic_entry.size = 0; // FIXME: what would stat say? + // FIXME: modified time? + return &m_synthetic_entry; + } + else + { + return read_excluding_parent(); + } + } + osd::directory::entry m_synthetic_entry; -// ------------------------------------------------- -// next_path_char - lexes out the next path -// character, normalizing separators as '/' -// ------------------------------------------------- +private: + virtual osd::directory::entry const *read_excluding_parent() = 0; + + bool m_returned_parent; +}; -/** - * @fn static char next_path_char(const char *s, int *pos) - * - * @brief Next path character. - * - * @param s The const char * to process. - * @param [in,out] pos If non-null, the position. - * - * @return A char. - */ -static char next_path_char(std::string const &s, std::string::size_type &pos) +class archive_directory_impl : public zippath_directory_base { - // skip over any initial separators - if (pos == 0) +public: + archive_directory_impl(archive_file::ptr &&zipfile, std::string &&zipprefix) : + zippath_directory_base(true), + m_zipfile(std::move(zipfile)), + m_zipprefix(std::move(zipprefix)) { - while ((pos < s.length()) && is_zip_file_separator(s[pos])) - pos++; } - // are we at a path separator? - if (pos == s.length()) - { - // return NUL - return '\0'; - } - else if (is_zip_file_separator(s[pos])) - { - // skip over path separators - while((pos < s.length()) && is_zip_file_separator(s[pos])) - pos++; + virtual bool is_archive() const override { return true; } - // normalize as '/' - return '/'; - } - else +private: + virtual osd::directory::entry const *read_excluding_parent() override { - // return character - return std::tolower(s[pos++]); - } -} + osd::directory::entry const *result = nullptr; + char const *relpath = nullptr; + do + { + // a zip file read + int header; + do + { + header = m_called_zip_first ? m_zipfile->next_file() : m_zipfile->first_file(); + m_called_zip_first = true; + relpath = nullptr; + } + while ((header >= 0) && ((relpath = get_relative_path()) == nullptr)); + if (relpath) + { + // we've found a ZIP entry; but this may be an entry deep within the target directory + char const *separator = relpath; + while (*separator && !is_zip_file_separator(*separator)) separator++; + if (*separator || m_zipfile->current_is_directory()) + { + // a nested entry; loop through returned_dirlist to see if we've returned the parent directory + auto const len(separator - relpath); + auto rdent = m_returned_dirlist.begin(); + while (m_returned_dirlist.end() != rdent) + { + if ((rdent->length() == len) && !core_strnicmp(rdent->c_str(), relpath, len)) + break; + else + ++rdent; + } + if (m_returned_dirlist.end() == rdent) + { + // we've found a new directory; add this to returned_dirlist + m_returned_dirlist.emplace_front(relpath, separator - relpath); -// ------------------------------------------------- -// zippath_find_sub_path - attempts to identify the -// type of a sub path in a zip file -// ------------------------------------------------- + // ...and return it + m_synthetic_entry.name = m_returned_dirlist.front().c_str(); + m_synthetic_entry.type = osd::directory::entry::entry_type::DIR; + m_synthetic_entry.size = 0; // FIXME: what would stat say? + // FIXME: modified time? + result = &m_synthetic_entry; + } + } + else + { + // a real file + m_synthetic_entry.name = relpath; + m_synthetic_entry.type = osd::directory::entry::entry_type::FILE; + m_synthetic_entry.size = m_zipfile->current_uncompressed_length(); + m_synthetic_entry.last_modified = m_zipfile->current_last_modified(); + result = &m_synthetic_entry; + } + } + } + while (relpath && !result); + return result; + } -/** - * @fn static const zip_file_header *zippath_find_sub_path(archive_file *zipfile, const char *subpath, osd::directory::entry::entry_type *type) - * - * @brief Zippath find sub path. - * - * @param [in,out] zipfile If non-null, the zipfile. - * @param subpath The subpath. - * @param [in,out] type If non-null, the type. - * - * @return null if it fails, else a zip_file_header*. - */ -static int zippath_find_sub_path(archive_file &zipfile, std::string const &subpath, osd::directory::entry::entry_type &type) -{ - for (int header = zipfile.first_file(); header >= 0; header = zipfile.next_file()) + // ------------------------------------------------- + // get_relative_path - checks to see if a specified + // header is in the zippath_directory, and if so + // returns the relative path + // ------------------------------------------------- + + char const *get_relative_path() const { - std::string::size_type i = 0, j = 0; - char c1, c2; - do + std::string::size_type len = m_zipprefix.length(); + char const *prefix = m_zipprefix.c_str(); + while (is_zip_file_separator(*prefix)) { - c1 = next_path_char(zipfile.current_name(), i); - c2 = next_path_char(subpath, j); + len--; + prefix++; } - while ((c1 == c2) && c1 && c2); - if (!c2 || ((c2 == '/') && !(c2 = next_path_char(subpath, j)))) + std::string const ¤t(m_zipfile->current_name()); + char const *result = current.c_str() + len; + if ((current.length() >= len) && + !strncmp(prefix, current.c_str(), len) && + (!*prefix || is_zip_file_separator(*result) || is_zip_file_separator(m_zipprefix.back()))) { - if (!c1) - { - type = zipfile.current_is_directory() ? osd::directory::entry::entry_type::DIR : osd::directory::entry::entry_type::FILE; - return header; - } - else if ((c1 == '/') || (i <= 1U)) - { - type = osd::directory::entry::entry_type::DIR; - return header; - } + while (is_zip_file_separator(*result)) + result++; + + return *result ? result : nullptr; + } + else + { + return nullptr; } } - type = osd::directory::entry::entry_type::NONE; - return -1; -} - + bool m_called_zip_first = false; + archive_file::ptr const m_zipfile; + std::string const m_zipprefix; + std::forward_list m_returned_dirlist; +}; -// ------------------------------------------------- -// zippath_resolve - separates a ZIP path out into -// true path and ZIP entry components -// ------------------------------------------------- - -/** - * @fn static osd_file::error zippath_resolve(const char *path, osd::directory::entry::entry_type &entry_type, archive_file *&zipfile, std::string &newpath) - * - * @brief Zippath resolve. - * - * @param path Full pathname of the file. - * @param [in,out] entry_type Type of the entry. - * @param [in,out] zipfile [in,out] If non-null, the zipfile. - * @param [in,out] newpath The newpath. - * - * @return A osd_file::error. - */ -static osd_file::error zippath_resolve(const char *path, osd::directory::entry::entry_type &entry_type, archive_file::ptr &zipfile, std::string &newpath) +class filesystem_diectory_impl : public zippath_directory_base { - newpath.clear(); +public: + filesystem_diectory_impl(std::string const &path) : zippath_directory_base(!is_root(path)), m_directory(osd::directory::open(path)) + { + } - // be conservative - entry_type = osd::directory::entry::entry_type::NONE; - zipfile.reset(); + virtual bool is_archive() const override { return false; } - std::string apath(path); - std::string apath_trimmed; - osd::directory::entry::entry_type current_entry_type; - bool went_up = false; - do + explicit operator bool() const { return bool(m_directory); } + +private: + virtual osd::directory::entry const *read_excluding_parent() override { - // trim the path of trailing path separators - auto i = apath.length(); - while ((i > 1) && is_path_separator(apath[i - 1])) - i--; - apath.resize(i); - apath_trimmed = apath; + assert(m_directory); - // stat the path - auto current_entry = osd_stat(apath_trimmed); + // a normal directory read + osd::directory::entry const *result = nullptr; + do + { + result = m_directory->read(); + } + while (result && (!strcmp(result->name, ".") || !strcmp(result->name, ".."))); - // did we find anything? - if (current_entry) + // special case - is this entry a ZIP file? if so we need to return it as a "directory" + if (result && (is_zip_file(result->name) || is_7z_file(result->name))) { - // get the entry type and free the stat entry - current_entry_type = current_entry->type; + // copy; but change the entry type + m_synthetic_entry = *result; + m_synthetic_entry.type = osd::directory::entry::entry_type::DIR; + m_synthetic_entry.size = 0; // FIXME: what would stat say? + return &m_synthetic_entry; } else { - // if we have not found the file or directory, go up - current_entry_type = osd::directory::entry::entry_type::NONE; - went_up = true; - apath = zippath_parent(apath); + return result; } } - while ((current_entry_type == osd::directory::entry::entry_type::NONE) && !is_root(apath.c_str())); - // if we did not find anything, then error out - if (current_entry_type == osd::directory::entry::entry_type::NONE) - return osd_file::error::NOT_FOUND; + osd::directory::ptr const m_directory; +}; - // is this file a ZIP file? - if ((current_entry_type == osd::directory::entry::entry_type::FILE) && - ((is_zip_file(apath_trimmed) && (archive_file::open_zip(apath_trimmed, zipfile) == archive_file::error::NONE)) || - (is_7z_file(apath_trimmed) && (archive_file::open_7z(apath_trimmed, zipfile) == archive_file::error::NONE)))) - { - auto i = strlen(path + apath.length()); - while ((i > 0) && is_zip_path_separator(path[apath.length() + i - 1])) - i--; - newpath.assign(path + apath.length(), i); +} // anonymous namespace - // this was a true ZIP path - attempt to identify the type of path - zippath_find_sub_path(*zipfile, newpath, current_entry_type); - if (current_entry_type == osd::directory::entry::entry_type::NONE) - return osd_file::error::NOT_FOUND; - } - else + +// ------------------------------------------------- +// zippath_directory::open - opens a directory +// ------------------------------------------------- + +osd_file::error zippath_directory::open(std::string const &path, ptr &directory) +{ + try { - // this was a normal path - if (went_up) + // resolve the path + osd::directory::entry::entry_type entry_type; + archive_file::ptr zipfile; + std::string zipprefix; + osd_file::error const err = zippath_resolve(path.c_str(), entry_type, zipfile, zipprefix); + if (osd_file::error::NONE != err) + return err; + + // we have to be a directory + if (osd::directory::entry::entry_type::DIR != entry_type) return osd_file::error::NOT_FOUND; - newpath = path; + // was the result a ZIP? + if (zipfile) + { + directory = std::make_unique(std::move(zipfile), std::move(zipprefix)); + return osd_file::error::NONE; + } + else + { + // a conventional directory + std::unique_ptr result(new filesystem_diectory_impl(path)); + if (!*result) + return osd_file::error::FAILURE; + + directory = std::move(result); + return osd_file::error::NONE; + } } + catch (std::bad_alloc const &) + { + return osd_file::error::OUT_OF_MEMORY; + } +} - // success! - entry_type = current_entry_type; - return osd_file::error::NONE; + +// ------------------------------------------------- +// zippath_directory::~zippath_directory - closes +// a directory +// ------------------------------------------------- + +zippath_directory::~zippath_directory() +{ } // ------------------------------------------------- -// zippath_opendir - opens a directory +// zippath_parent - retrieves the parent directory // ------------------------------------------------- -/** - * @fn osd_file::error zippath_opendir(const std::string &path, zippath_directory **directory) - * - * @brief Zippath opendir. - * - * @param path Full pathname of the file. - * @param [in,out] directory If non-null, pathname of the directory. - * - * @return A osd_file::error. - */ - -osd_file::error zippath_opendir(const std::string &path, zippath_directory **directory) +std::string &zippath_parent(std::string &dst, const std::string &path) { - osd_file::error err; + std::string::size_type pos; + parse_parent_path(path, &pos, nullptr); - /* allocate a directory */ - zippath_directory *result = nullptr; - try - { - result = new zippath_directory; - } - catch (std::bad_alloc &) - { - err = osd_file::error::OUT_OF_MEMORY; - goto done; - } - /* resolve the path */ - osd::directory::entry::entry_type entry_type; - err = zippath_resolve(path.c_str(), entry_type, result->zipfile, result->zipprefix); - if (err != osd_file::error::NONE) - goto done; + if (pos != std::string::npos) + dst = path.substr(0, pos + 1); + else + dst.clear(); + return dst; +} - /* we have to be a directory */ - if (entry_type != osd::directory::entry::entry_type::DIR) - { - err = osd_file::error::NOT_FOUND; - goto done; - } - /* was the result a ZIP? */ - if (!result->zipfile) - { - /* a conventional directory */ - result->directory = osd::directory::open(path); - if (!result->directory) - { - err = osd_file::error::FAILURE; - goto done; - } - /* is this path the root? if so, pretend we've already returned the parent */ - if (is_root(path.c_str())) - result->returned_parent = true; - } +// ------------------------------------------------- +// zippath_parent - retrieves the parent directory +// ------------------------------------------------- -done: - if ((directory == nullptr || err != osd_file::error::NONE) && result != nullptr) - { - zippath_closedir(result); - result = nullptr; - } - if (directory != nullptr) - *directory = result; - return err; +std::string zippath_parent(const std::string &path) +{ + std::string result; + zippath_parent(result, path); + return result; } + // ------------------------------------------------- -// zippath_closedir - closes a directory +// zippath_parent_basename - retrieves the parent +// directory basename // ------------------------------------------------- /** - * @fn void zippath_closedir(zippath_directory *directory) + * @fn std::string &zippath_parent_basename(std::string &dst, const std::string &path) + * + * @brief Zippath parent basename. * - * @brief Zippath closedir. + * @param [in,out] dst Destination for the. + * @param path Full pathname of the file. * - * @param [in,out] directory If non-null, pathname of the directory. + * @return A std::string& */ -void zippath_closedir(zippath_directory *directory) +std::string &zippath_parent_basename(std::string &dst, const std::string &path) { - if (directory->directory != nullptr) - directory->directory.reset(); + std::string::size_type beginpos, endpos; + parse_parent_path(path, &beginpos, &endpos); + dst.copy((char*)(path.c_str() + beginpos + 1), endpos - beginpos); + return dst; +} - if (directory->zipfile != nullptr) - directory->zipfile.reset(); - directory->returned_dirlist.clear(); - delete directory; +// ------------------------------------------------- +// zippath_parent_basename - retrieves the parent +// directory basename +// ------------------------------------------------- + +std::string zippath_parent_basename(const std::string &path) +{ + std::string result; + zippath_parent_basename(result, path); + return result; } + // ------------------------------------------------- -// get_relative_path - checks to see if a specified -// header is in the zippath_directory, and if so -// returns the relative path +// zippath_combine - combines two paths // ------------------------------------------------- /** - * @fn static const char *get_relative_path(zippath_directory *directory, const zip_file_header *header) + * @fn std::string &zippath_combine(std::string &dst, const char *path1, const char *path2) * - * @brief Gets relative path. + * @brief Zippath combine. * - * @param [in,out] directory If non-null, pathname of the directory. - * @param header The header. + * @param [in,out] dst Destination for the. + * @param path1 The first path. + * @param path2 The second path. * - * @return null if it fails, else the relative path. + * @return A std::string& */ -static const char *get_relative_path(zippath_directory const &directory) +std::string &zippath_combine(std::string &dst, const std::string &path1, const std::string &path2) { - auto len = directory.zipprefix.length(); - char const *prefix = directory.zipprefix.c_str(); - while (is_zip_file_separator(*prefix)) + if (path2 == ".") { - len--; - prefix++; + dst.assign(path1); } - - std::string const ¤t(directory.zipfile->current_name()); - char const *result = directory.zipfile->current_name().c_str() + len; - if ((current.length() >= len) && - !strncmp(prefix, current.c_str(), len) && - (!*prefix || is_zip_file_separator(*result) || is_zip_file_separator(directory.zipprefix.back()))) + else if (path2 == "..") { - while (is_zip_file_separator(*result)) - result++; - - return *result ? result : nullptr; + dst = zippath_parent(path1); + } + else if (osd_is_absolute_path(path2)) + { + dst.assign(path2); + } + else if (!path1.empty() && !is_path_separator(*path1.rbegin())) + { + dst.assign(path1).append(PATH_SEPARATOR).append(path2); } else { - return nullptr; + dst.assign(path1).append(path2); } + return dst; +} + + + +// ------------------------------------------------- +// zippath_combine - combines two paths +// ------------------------------------------------- + +std::string zippath_combine(const std::string &path1, const std::string &path2) +{ + std::string result; + zippath_combine(result, path1, path2); + return result; } + +/*************************************************************************** + FILE OPERATIONS +***************************************************************************/ + // ------------------------------------------------- -// zippath_readdir - reads a directory +// zippath_fopen - opens a zip path file // ------------------------------------------------- /** - * @fn const osd::directory::entry *zippath_readdir(zippath_directory *directory) + * @fn osd_file::error zippath_fopen(const std::string &filename, uint32_t openflags, util::core_file::ptr &file, std::string &revised_path) * - * @brief Zippath readdir. + * @brief Zippath fopen. * - * @param [in,out] directory If non-null, pathname of the directory. + * @param filename Filename of the file. + * @param openflags The openflags. + * @param [in,out] file [in,out] If non-null, the file. + * @param [in,out] revised_path Full pathname of the revised file. * - * @return null if it fails, else an osd::directory::entry*. + * @return A osd_file::error. */ -const osd::directory::entry *zippath_readdir(zippath_directory *directory) +osd_file::error zippath_fopen(const std::string &filename, uint32_t openflags, util::core_file::ptr &file, std::string &revised_path) { - const osd::directory::entry *result = nullptr; + osd_file::error filerr = osd_file::error::NOT_FOUND; + archive_file::error ziperr; + archive_file::ptr zip; + int header; + osd::directory::entry::entry_type entry_type; + int len; - if (!directory->returned_parent) - { - /* first thing's first - return parent directory */ - directory->returned_parent = true; - directory->returned_entry.name = ".."; - directory->returned_entry.type = osd::directory::entry::entry_type::DIR; - directory->returned_entry.size = 0; // FIXME: what would stat say? - // FIXME: modified time? - result = &directory->returned_entry; - } - else if (directory->directory) - { - /* a normal directory read */ - do - { - result = directory->directory->read(); - } - while (result && (!strcmp(result->name, ".") || !strcmp(result->name, ".."))); + /* first, set up the two types of paths */ + std::string mainpath(filename); + std::string subpath; + file = nullptr; - /* special case - is this entry a ZIP file? if so we need to return it as a "directory" */ - if (result && (is_zip_file(result->name) || is_7z_file(result->name))) - { - /* copy; but change the entry type */ - directory->returned_entry = *result; - directory->returned_entry.type = osd::directory::entry::entry_type::DIR; - directory->returned_entry.size = 0; // FIXME: what would stat say? - // FIXME: modified time? - result = &directory->returned_entry; - } - } - else if (directory->zipfile) + /* loop through */ + while((file == nullptr) && (mainpath.length() > 0) + && ((openflags == OPEN_FLAG_READ) || (subpath.length() == 0))) { - char const *relpath; - do + /* is the mainpath a ZIP path? */ + if (is_zip_file(mainpath) || is_7z_file(mainpath)) { - /* a zip file read */ - int header; - do - { - header = directory->called_zip_first ? directory->zipfile->next_file() : directory->zipfile->first_file(); - directory->called_zip_first = true; - relpath = nullptr; - } - while ((header >= 0) && ((relpath = get_relative_path(*directory)) == nullptr)); - - if (relpath) + /* this file might be a zip file - lets take a look */ + ziperr = is_zip_file(mainpath) ? archive_file::open_zip(mainpath, zip) : archive_file::open_7z(mainpath, zip); + if (ziperr == archive_file::error::NONE) { - /* we've found a ZIP entry; but this may be an entry deep within the target directory */ - char const *separator = relpath; - while (*separator && !is_zip_file_separator(*separator)) separator++; - - if (*separator || directory->zipfile->current_is_directory()) + /* it is a zip file - error if we're not opening for reading */ + if (openflags != OPEN_FLAG_READ) { - /* a nested entry; loop through returned_dirlist to see if we've returned the parent directory */ - auto const len(separator - relpath); - auto rdent = directory->returned_dirlist.begin(); - while (directory->returned_dirlist.end() != rdent) - { - if ((rdent->length() == len) && !core_strnicmp(rdent->c_str(), relpath, len)) - break; - else - ++rdent; - } - - if (directory->returned_dirlist.end() == rdent) - { - /* we've found a new directory; add this to returned_dirlist */ - directory->returned_dirlist.emplace_front(relpath, separator - relpath); - - /* ...and return it */ - directory->returned_entry.name = directory->returned_dirlist.front().c_str(); - directory->returned_entry.type = osd::directory::entry::entry_type::DIR; - directory->returned_entry.size = 0; // FIXME: what would stat say? - // FIXME: modified time? - result = &directory->returned_entry; - } + filerr = osd_file::error::ACCESS_DENIED; + goto done; } + + if (subpath.length() > 0) + header = zippath_find_sub_path(*zip, subpath, entry_type); else + header = zip->first_file(); + + if (header < 0) { - /* a real file */ - directory->returned_entry.name = relpath; - directory->returned_entry.type = osd::directory::entry::entry_type::FILE; - directory->returned_entry.size = directory->zipfile->current_uncompressed_length(); - directory->returned_entry.last_modified = directory->zipfile->current_last_modified(); - result = &directory->returned_entry; + filerr = osd_file::error::NOT_FOUND; + goto done; } + + /* attempt to read the file */ + filerr = create_core_file_from_zip(*zip, file); + if (filerr != osd_file::error::NONE) + goto done; + + /* update subpath, if appropriate */ + if (subpath.length() == 0) + subpath.assign(zip->current_name()); + + /* we're done */ + goto done; } } - while (relpath && !result); - } - return result; -} + if (subpath.length() == 0) + filerr = util::core_file::open(filename, openflags, file); + else + filerr = osd_file::error::NOT_FOUND; + /* if we errored, then go up a directory */ + if (filerr != osd_file::error::NONE) + { + /* go up a directory */ + auto temp = zippath_parent(mainpath); -// ------------------------------------------------- -// zippath_is_zip - returns true if this path is -// a ZIP path or false if not -// ------------------------------------------------- + /* append to the sub path */ + if (subpath.length() > 0) + { + std::string temp2; + mainpath = mainpath.substr(temp.length()); + temp2.assign(mainpath).append(PATH_SEPARATOR).append(subpath); + subpath.assign(temp2); + } + else + { + mainpath = mainpath.substr(temp.length()); + subpath.assign(mainpath); + } + /* get the new main path, truncating path separators */ + len = temp.length(); + while (len > 0 && is_zip_file_separator(temp[len - 1])) + len--; + temp = temp.substr(0, len); + mainpath.assign(temp); + } + } -/** - * @fn int zippath_is_zip(zippath_directory *directory) - * - * @brief Zippath is zip. - * - * @param [in,out] directory If non-null, pathname of the directory. - * - * @return An int. - */ +done: + /* store the revised path */ + revised_path.clear(); + if (filerr == osd_file::error::NONE) + { + /* cannonicalize mainpath */ + std::string alloc_fullpath; + filerr = osd_get_full_path(alloc_fullpath, mainpath); + if (filerr == osd_file::error::NONE) + { + revised_path = alloc_fullpath; + if (subpath.length() > 0) + revised_path.append(PATH_SEPARATOR).append(subpath); + } + } -bool zippath_is_zip(zippath_directory *directory) -{ - return directory->zipfile != nullptr; + return filerr; } } // namespace util diff --git a/src/lib/util/zippath.h b/src/lib/util/zippath.h index 3bef7fc9d39..cf6c2249f9c 100644 --- a/src/lib/util/zippath.h +++ b/src/lib/util/zippath.h @@ -8,22 +8,40 @@ ***************************************************************************/ -#pragma once - #ifndef MAME_LIB_UTIL_ZIPPATH_H #define MAME_LIB_UTIL_ZIPPATH_H +#pragma once + #include "corefile.h" -#include #include "unzip.h" +#include + namespace util { + /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ -class zippath_directory; +class zippath_directory +{ +public: + typedef std::unique_ptr ptr; + + // opens a directory + static osd_file::error open(std::string const &path, ptr &directory); + + // closes a directory + virtual ~zippath_directory(); + + // reads a directory entry + virtual osd::directory::entry const *readdir() = 0; + + // returns true if this directory is an archive or false if it is a filesystem directory + virtual bool is_archive() const = 0; +}; @@ -51,22 +69,6 @@ std::string zippath_combine(const std::string &path1, const std::string &path2); // opens a zip path file osd_file::error zippath_fopen(const std::string &filename, uint32_t openflags, util::core_file::ptr &file, std::string &revised_path); - -// ----- directory operations ----- */ - -// opens a directory -osd_file::error zippath_opendir(const std::string &path, zippath_directory **directory); - -// closes a directory -void zippath_closedir(zippath_directory *directory); - -// reads a directory entry -const osd::directory::entry *zippath_readdir(zippath_directory *directory); - -// returns true if this path is a ZIP path or false if not -bool zippath_is_zip(zippath_directory *directory); - } // namespace util - #endif // MAME_LIB_UTIL_ZIPPATH_H -- cgit v1.2.3