diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dcd0c1..f873cc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,7 @@ if(MSVC) if(NOT MSVC_VERSION LESS 1600) # MSVC 10+ set(INNOEXTRACT_STD_BITSET_CONSTRUCT_TYPE "unsigned long long") + set(INNOEXTRACT_HAVE_STD_CODECVT_UTF8_UTF16 ON) endif() elseif(${Boost_VERSION} LESS 104800) # Older Boost versions don't work with C++11 @@ -152,6 +153,7 @@ elseif(USE_CXX11) if(FLAG_FOUND) set(INNOEXTRACT_STD_BITSET_CONSTRUCT_TYPE "unsigned long long") endif() + # missing check for INNOEXTRACT_HAVE_STD_CODECVT_UTF8_UTF16 endif() # Don't expose internal symbols to the outside world by default @@ -167,6 +169,11 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") add_definitions(-D_GNU_SOURCE=1) endif() +if(WIN32) + # Define this so that we don't accitenally use ANSI functions + add_definitions(-D_UNICODE) +endif() + # Check for optional functionality and system configuration @@ -264,6 +271,10 @@ if(LZMA_FOUND) list(APPEND INNOEXTRACT_SOURCES src/stream/lzma.cpp) endif() +if(WIN32) + list(APPEND INNOEXTRACT_SOURCES src/util/windows.cpp) +endif() + file(GLOB_RECURSE ALL_INCLUDES "${CMAKE_SOURCE_DIR}/src/*.hpp") list(SORT INNOEXTRACT_SOURCES) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 9cb2ac0..5d90202 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -36,7 +35,6 @@ #include #include #include -#include #include #include "release.hpp" @@ -58,20 +56,24 @@ #include "util/boostfs_compat.hpp" #include "util/console.hpp" +#include "util/fstream.hpp" #include "util/load.hpp" #include "util/log.hpp" #include "util/output.hpp" #include "util/time.hpp" +#include "util/windows.hpp" namespace fs = boost::filesystem; namespace po = boost::program_options; + enum ExitValues { ExitSuccess = 0, ExitUserError = 1, ExitDataError = 2 }; + static const char * get_command(const char * argv0) { if(!argv0) { @@ -98,6 +100,7 @@ static const char * get_command(const char * argv0) { } } + static void print_version() { std::cout << color::white << innoextract_name << ' ' << innoextract_version << color::reset @@ -109,6 +112,7 @@ static void print_version() { << innosetup_versions << color::reset << '\n'; } + static void print_help(const char * name, const po::options_description & visible) { std::cout << color::white << "Usage: " << name << " [options] \n\n" << color::reset; @@ -124,6 +128,7 @@ static void print_help(const char * name, const po::options_description & visibl std::cout << "This is free software with absolutely no warranty.\n"; } + static void print_license() { std::cout << color::white << innoextract_name @@ -133,6 +138,7 @@ static void print_license() { ; } + struct options { bool silent; @@ -152,10 +158,11 @@ struct options { }; + struct file_output { fs::path name; - fs::ofstream stream; + util::ofstream stream; explicit file_output(const fs::path & file) : name(file) { try { @@ -164,7 +171,7 @@ struct file_output { throw std::runtime_error("error creating directories for \"" + name.string() + '"'); } - stream.open(name); + stream.open(name, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); if(!stream.is_open()) { throw std::runtime_error("error opening output file \"" + name.string() + '"'); } @@ -172,13 +179,14 @@ struct file_output { }; + static void process_file(const fs::path & file, const options & o) { if(fs::is_directory(file)) { throw std::runtime_error("input file \"" + file.string() + "\" is a directory"); } - fs::ifstream ifs(file, std::ios_base::in | std::ios_base::binary | std::ios_base::ate); + util::ifstream ifs(file, std::ios_base::in | std::ios_base::binary); if(!ifs.is_open()) { throw std::runtime_error("error opening file \"" + file.string() + '"'); } @@ -241,7 +249,7 @@ static void process_file(const fs::path & file, const options & o) { boost::scoped_ptr slice_reader; if(o.extract || o.test) { if(offsets.data_offset) { - slice_reader.reset(new stream::slice_reader(file, offsets.data_offset)); + slice_reader.reset(new stream::slice_reader(&ifs, offsets.data_offset)); } else { slice_reader.reset(new stream::slice_reader(file.parent_path(), file.stem(), info.header.slices_per_disk)); @@ -255,12 +263,10 @@ static void process_file(const fs::path & file, const options & o) { debug("[starting " << chunk.first.compression << " chunk @ slice " << chunk.first.first_slice << " + " << print_hex(offsets.data_offset) << " + " << print_hex(chunk.first.offset) << ']'); - stream::chunk_reader::pointer chunk_source; if(o.extract || o.test) { chunk_source = stream::chunk_reader::get(*slice_reader, chunk.first); } - boost::uint64_t offset = 0; BOOST_FOREACH(const Files::value_type & location, chunk.second) { @@ -341,6 +347,9 @@ static void process_file(const fs::path & file, const options & o) { BOOST_FOREACH(const file_t & path, output_names) { std::cout << color::white << path.first.string() << color::reset << '\n'; } + if(o.extract || o.test) { + std::cout.flush(); + } extract_progress.update(0, true); } @@ -421,6 +430,7 @@ static void process_file(const fs::path & file, const options & o) { extract_progress.clear(); } + int main(int argc, char * argv[]) { ::options o; diff --git a/src/configure.hpp.in b/src/configure.hpp.in index e9b2f23..16d22b8 100644 --- a/src/configure.hpp.in +++ b/src/configure.hpp.in @@ -28,6 +28,7 @@ // C++11 functionality #define INNOEXTRACT_STD_BITSET_CONSTRUCT_TYPE ${INNOEXTRACT_STD_BITSET_CONSTRUCT_TYPE} +#cmakedefine01 INNOEXTRACT_HAVE_STD_CODECVT_UTF8_UTF16 // Optional dependencies #cmakedefine01 INNOEXTRACT_HAVE_LZMA diff --git a/src/stream/slice.cpp b/src/stream/slice.cpp index 5972d42..fb0024e 100644 --- a/src/stream/slice.cpp +++ b/src/stream/slice.cpp @@ -42,27 +42,29 @@ const char slice_ids[][8] = { } // anonymous namespace -slice_reader::slice_reader(const path_type & setup_file, boost::uint32_t data_offset) +slice_reader::slice_reader(std::istream * istream, boost::uint32_t data_offset) : dir(), last_dir(), base_file(), data_offset(data_offset), slices_per_disk(1), - current_slice(0) { - - ifs.open(setup_file, std::ios_base::binary | std::ios_base::in | std::ios_base::ate); + current_slice(0), is(istream) { std::streampos max_size = std::streampos(std::numeric_limits::max()); - slice_size = boost::uint32_t(std::min(ifs.tellg(), max_size)); - if(ifs.seekg(data_offset).fail()) { - ifs.close(); + + std::streampos file_size = is->seekg(0, std::ios_base::end).tellg(); + + slice_size = boost::uint32_t(std::min(file_size, max_size)); + if(is->seekg(data_offset).fail()) { + log_error << "could not seek to data"; } } slice_reader::slice_reader(const path_type & dir, const path_type & base_file, size_t slices_per_disk) : dir(dir), last_dir(dir), base_file(base_file), data_offset(0), - slices_per_disk(slices_per_disk), current_slice(0) { } + slices_per_disk(slices_per_disk), current_slice(0), slice_size(0), + is(&ifs) { } bool slice_reader::seek(size_t slice) { - if(slice == current_slice && ifs.is_open()) { + if(slice == current_slice && is_open()) { return true; } @@ -79,13 +81,14 @@ bool slice_reader::open_file(const path_type & file) { log_info << "opening \"" << color::cyan << file.string() << color::reset << '"'; ifs.close(); + ifs.clear(); ifs.open(file, std::ios_base::in | std::ios_base::binary | std::ios_base::ate); if(ifs.fail()) { return false; } - std::streampos fileSize = ifs.tellg(); + std::streampos file_size = ifs.tellg(); ifs.seekg(0); char magic[8]; @@ -108,8 +111,16 @@ bool slice_reader::open_file(const path_type & file) { } slice_size = load_number(ifs); - if(ifs.fail() || std::streampos(slice_size) > fileSize) { - log_error << "[slice] bad slice size: " << slice_size << " > " << fileSize; + if(ifs.fail()) { + log_error << "[slice] error reading slice size"; + ifs.close(); + return false; + } else if(std::streampos(slice_size) > file_size) { + log_error << "[slice] bad slice size: " << slice_size << " > " << file_size; + ifs.close(); + return false; + } else if(std::streampos(slice_size) < ifs.tellg()) { + log_error << "[slice] bad slice size: " << slice_size << " < " << ifs.tellg(); ifs.close(); return false; } @@ -124,11 +135,11 @@ bool slice_reader::open_file(const path_type & file) { bool slice_reader::open(size_t slice, const path_type & file) { current_slice = slice; + is = &ifs; ifs.close(); if(slices_per_disk == 0) { - log_error << "[slice] slices per disk must not be zero"; - return false; + throw std::runtime_error("[slice] slices per disk must not be zero"); } path_type slice_file = file; @@ -157,6 +168,12 @@ bool slice_reader::open(size_t slice, const path_type & file) { return true; } + if(dir != last_dir) { + log_error << "error opening " << slice_file << " in " << last_dir << " or " << dir; + } else { + log_error << "error opening " << last_dir / slice_file; + } + return false; } @@ -166,11 +183,13 @@ bool slice_reader::seek(size_t slice, boost::uint32_t offset) { return false; } - if(offset > slice_size - data_offset) { + offset += data_offset; + + if(offset > slice_size) { return false; } - if(ifs.seekg(data_offset + offset).fail()) { + if(is->seekg(offset).fail()) { return false; } @@ -187,16 +206,27 @@ std::streamsize slice_reader::read(char * buffer, std::streamsize bytes) { while(bytes > 0) { - std::streamsize remaining = std::streamsize(slice_size - size_t(ifs.tellg())); + boost::uint32_t read_pos = boost::uint32_t(is->tellg()); + if(read_pos > slice_size) { + return -1; + } + std::streamsize remaining = std::streamsize(slice_size - read_pos); if(!remaining) { if(!seek(current_slice + 1)) { return nread; } - remaining = std::streamsize(slice_size - size_t(ifs.tellg())); + read_pos = boost::uint32_t(is->tellg()); + if(read_pos > slice_size) { + return -1; + } + remaining = std::streamsize(slice_size - read_pos); } - std::streamsize read = ifs.read(buffer, std::min(remaining, bytes)).gcount(); + if(is->read(buffer, std::min(remaining, bytes)).fail()) { + return -1; + } + std::streamsize read = is->gcount(); nread += read, buffer += read, bytes -= read; } diff --git a/src/stream/slice.hpp b/src/stream/slice.hpp index d044aed..1d4af2c 100644 --- a/src/stream/slice.hpp +++ b/src/stream/slice.hpp @@ -23,7 +23,8 @@ #include #include -#include + +#include "util/fstream.hpp" namespace stream { @@ -41,14 +42,16 @@ class slice_reader : public boost::iostreams::source { path_type slice_file; boost::uint32_t slice_size; - boost::filesystem::ifstream ifs; + util::ifstream ifs; + + std::istream * is; bool seek(size_t slice); bool open_file(const path_type & file); public: - slice_reader(const path_type & setup_file, boost::uint32_t data_offset); + slice_reader(std::istream * istream, boost::uint32_t data_offset); slice_reader(const path_type & dir, const path_type & base_file, size_t slices_per_disk); @@ -61,7 +64,7 @@ public: bool open(size_t slice, const path_type & slice_file); - bool is_open() { return ifs.is_open(); } + bool is_open() { return (is != &ifs || ifs.is_open()); } }; diff --git a/src/util/fstream.hpp b/src/util/fstream.hpp new file mode 100644 index 0000000..18bc820 --- /dev/null +++ b/src/util/fstream.hpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 Daniel Scharrer + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the author(s) be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/*! + * boost::filesystems::{i,o,}fstream doesn't support unicode names on windows + * Implement our own wrapper using boost::iostreams. + */ +#ifndef INNOEXTRACT_UTIL_FSTREAM_HPP +#define INNOEXTRACT_UTIL_FSTREAM_HPP + +#if !defined(_WIN32) + +#include + +namespace util { + +typedef boost::filesystem::ifstream ifstream; +typedef boost::filesystem::ofstream ofstream; +typedef boost::filesystem::fstream fstream; + +} // namespace util + +#else // if defined(_WIN32) + +#include +#include +#include + +namespace util { + +/*! + * {i,o,}fstream implementation with support for Unicode filenames. + * Create a subclass instead of a typedef to force boost::filesystem::path parameters. + */ +template +class path_fstream : public boost::iostreams::stream { + +private: // disallow copying + + path_fstream(const path_fstream &); + const path_fstream & operator=(const path_fstream &); + + typedef boost::filesystem::path path; + typedef boost::iostreams::stream base; + + Device & device() { return **this; } + + void fix_open_mode(std::ios_base::openmode mode); + +public: + + path_fstream() : base(Device()) { } + + explicit path_fstream(const path & p) : base(p) { } + + path_fstream(const path & p, std::ios_base::openmode mode) : base(p, mode) { + fix_open_mode(mode); + } + + void open(const path & p) { + base::close(); + base::open(p); + } + + void open(const path & p, std::ios_base::openmode mode) { + base::close(); + base::open(p, mode); + fix_open_mode(mode); + } + + bool is_open() { + return device().is_open(); // return the real open state, not base::is_open() + } + + virtual ~path_fstream() { } +}; + +template <> +inline void path_fstream + ::fix_open_mode(std::ios_base::openmode mode) { + if((mode & std::ios_base::ate) && is_open()) { + seekg(0, std::ios_base::end); + } +} + +template <> +inline void path_fstream + ::fix_open_mode(std::ios_base::openmode mode) { + if((mode & std::ios_base::ate) && is_open()) { + seekp(0, std::ios_base::end); + } +} + +template <> +inline void path_fstream + ::fix_open_mode(std::ios_base::openmode mode) { + if((mode & std::ios_base::ate) && is_open()) { + seekg(0, std::ios_base::end); + seekp(0, std::ios_base::end); + } +} + +typedef path_fstream ifstream; +typedef path_fstream ofstream; +typedef path_fstream fstream; + +} // namespace util + +#endif // defined(_WIN32) + +#endif // INNOEXTRACT_UTIL_FSTREAM_HPP diff --git a/src/util/windows.cpp b/src/util/windows.cpp new file mode 100644 index 0000000..e25d4bb --- /dev/null +++ b/src/util/windows.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Daniel Scharrer + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the author(s) be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef _WIN32 +#define _WIN32 +#endif +#include "util/windows.hpp" + +#include +#include + +#include + +#include + +#include "configure.hpp" + +#if INNOEXTRACT_HAVE_STD_CODECVT_UTF8_UTF16 +// C++11 +#include +namespace { typedef std::codecvt_utf8_utf16 utf8_codecvt; } +#else +// Using private Boost stuff - bad, but meh. +#include +namespace { typedef boost::filesystem::detail::utf8_codecvt_facet utf8_codecvt; } +#endif + +// We really want main here, not utf8_main. +#undef main +int main() { + + // We use UTF-8 for everything internally, as almost all modern operating systems + // have standardized on that. However, as usual, Windows has to do its own thing + // and only supports Unicode input/output via UCS-2^H^H^H^H^HUTF-16. + + std::setlocale(LC_ALL, ""); + + // Get the UTF-16 command-line parameters and convert it them to UTF-8 ourself. + int argc = __argc; + wchar_t ** wargv = __wargv; + char ** argv = new char *[argc + 1]; + argv[argc] = NULL; + for(int i = 0; i < argc; i++) { + int n = WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, NULL); + argv[i] = new char[n]; + WideCharToMultiByte(CP_UTF8, 0, wargv[i], -1, argv[i], n, NULL, NULL); + } + + // Tell boost::filesystem to interpret our path strings as UTF-8. + std::locale global_locale = std::locale(); + std::locale utf8_locale(global_locale, new utf8_codecvt); + boost::filesystem::path::imbue(utf8_locale); + + return utf8_main(argc, argv); +} diff --git a/src/util/windows.hpp b/src/util/windows.hpp new file mode 100644 index 0000000..1db8542 --- /dev/null +++ b/src/util/windows.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 Daniel Scharrer + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the author(s) be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/*! + * Compatibility wrapper to work around deficiencies in Microsoft® Windows™. + * Mostly deals with converting between UTF-8 and UTF-16 input/output. + * More precisely: + * - Converts wide char command-line arguments to UTF-8 and calls utf8_main(). + * - Sets an UTF-8 locale for boost::filesystem::path. + * This makes everything in boost::filesystem UTF-8 aware, except for {i,o,}fstream. + * For those, there are UTF-8 aware implementations in util/fstream.hpp + */ +#ifndef INNOEXTRACT_UTIL_WINDOWS_HPP +#define INNOEXTRACT_UTIL_WINDOWS_HPP + +#if defined(_WIN32) + +//! Program entry point that will always receive UTF-8 encoded arguments. +int utf8_main(int argc, char * argv[]); + +//! We define our own wrapper main(), so rename the real one. +#define main utf8_main + +#endif // defined(_WIN32) + +#endif // INNOEXTRACT_UTIL_WINDOWS_HPP