Browse Source

Fix issues with Unicode filenames under Windows

Also, only open the main setup file once.
coverity_scan
Daniel Scharrer 13 years ago
parent
commit
a795d473db
  1. 11
      CMakeLists.txt
  2. 26
      src/cli/main.cpp
  3. 1
      src/configure.hpp.in
  4. 68
      src/stream/slice.cpp
  5. 11
      src/stream/slice.hpp
  6. 128
      src/util/fstream.hpp
  7. 72
      src/util/windows.cpp
  8. 43
      src/util/windows.hpp

11
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)

26
src/cli/main.cpp

@ -22,7 +22,6 @@
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
@ -36,7 +35,6 @@
#include <boost/scoped_ptr.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
#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] <setup file(s)>\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<stream::slice_reader> 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;

1
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

68
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<boost::int32_t>::max());
slice_size = boost::uint32_t(std::min<std::streampos>(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<boost::uint32_t>(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;
}

11
src/stream/slice.hpp

@ -23,7 +23,8 @@
#include <boost/iostreams/concepts.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#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()); }
};

128
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 <boost/filesystem/fstream.hpp>
namespace util {
typedef boost::filesystem::ifstream ifstream;
typedef boost::filesystem::ofstream ofstream;
typedef boost::filesystem::fstream fstream;
} // namespace util
#else // if defined(_WIN32)
#include <boost/filesystem/path.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
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 <typename Device>
class path_fstream : public boost::iostreams::stream<Device> {
private: // disallow copying
path_fstream(const path_fstream &);
const path_fstream & operator=(const path_fstream &);
typedef boost::filesystem::path path;
typedef boost::iostreams::stream<Device> 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<boost::iostreams::file_descriptor_source>
::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<boost::iostreams::file_descriptor_sink>
::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<boost::iostreams::file_descriptor>
::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<boost::iostreams::file_descriptor_source> ifstream;
typedef path_fstream<boost::iostreams::file_descriptor_sink> ofstream;
typedef path_fstream<boost::iostreams::file_descriptor> fstream;
} // namespace util
#endif // defined(_WIN32)
#endif // INNOEXTRACT_UTIL_FSTREAM_HPP

72
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 <locale>
#include <clocale>
#include <windows.h>
#include <boost/filesystem/path.hpp>
#include "configure.hpp"
#if INNOEXTRACT_HAVE_STD_CODECVT_UTF8_UTF16
// C++11
#include <codecvt>
namespace { typedef std::codecvt_utf8_utf16<wchar_t> utf8_codecvt; }
#else
// Using private Boost stuff - bad, but meh.
#include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
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);
}

43
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
Loading…
Cancel
Save