├── .gitignore ├── examples ├── .gitignore ├── ztxtpipe.cpp ├── zpipe.cpp ├── Makefile ├── test-strict_fstream.cpp └── zc.cpp ├── .travis.yml ├── .travis.Dockerfile.in ├── LICENSE ├── README.org └── src ├── strict_fstream.hpp └── zstr.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | /local 2 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | /test-strict_fstream 2 | /ztxtpipe 3 | /zpipe 4 | /zc 5 | -------------------------------------------------------------------------------- /examples/ztxtpipe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "zstr.hpp" 4 | 5 | int main() 6 | { 7 | // 8 | // Create zstr::istream feeding off std::cin. 9 | // 10 | zstr::istream is(std::cin); 11 | // 12 | // Main loop 13 | // 14 | std::string s; 15 | while (getline(is, s)) 16 | { 17 | std::cout << s << std::endl; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # travis.yml for github.com/mateidavid/zstr 2 | 3 | sudo: required 4 | 5 | services: 6 | - docker 7 | 8 | before_install: 9 | - sudo apt-get update -y 10 | - sudo apt-get install -y -o Dpkg::Options::="--force-confnew" docker-engine 11 | - TZ=$(cat /etc/timezone) USER_ID=$(id -u) USER_NAME=$(id -un) GROUP_ID=$(id -g) GROUP_NAME=$(id -gn) envsubst <.travis.Dockerfile.in | docker build -t zstr - 12 | 13 | install: 14 | - docker run --rm -v $PWD:/data zstr make -C examples 15 | 16 | script: 17 | - docker run --rm -v $PWD:/data zstr make -C examples test 18 | -------------------------------------------------------------------------------- /.travis.Dockerfile.in: -------------------------------------------------------------------------------- 1 | FROM debian:stable 2 | MAINTAINER Matei David 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | # install prerequisites 6 | RUN apt-get update && \ 7 | apt-get install -y \ 8 | build-essential \ 9 | zlib1g-dev 10 | 11 | # if necessary, specify compiler 12 | #RUN apt-get install -y g++-4.9 g++-5 g++-6 13 | #ENV CC=gcc-4.9 14 | #ENV CXX=g++-4.9 15 | 16 | # use host timezone 17 | ENV TZ=${TZ} 18 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} >/etc/timezone 19 | 20 | # use host id 21 | RUN groupadd --gid ${GROUP_ID} ${GROUP_NAME} 22 | RUN useradd --create-home --uid ${USER_ID} --gid ${GROUP_ID} ${USER_NAME} 23 | USER ${USER_NAME} 24 | 25 | VOLUME /data 26 | WORKDIR /data 27 | -------------------------------------------------------------------------------- /examples/zpipe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "zstr.hpp" 4 | 5 | int main() 6 | { 7 | // 8 | // Create explicit zstr::streambuf feeding off the streambuf of std::cin. 9 | // This syntax allows for setting the buffer size and the auto-detect option. 10 | // 11 | zstr::istreambuf zsbuf(std::cin.rdbuf(), 1<<16, true); 12 | // 13 | // Create an std::istream wrapper for the zstr::streambuf. 14 | // NOTE: A zstr::istream constructed with a zstr::streambuf parameter would decompress twice. 15 | // 16 | std::istream is(&zsbuf); 17 | // 18 | // Turn on error reporting (otherwise, zstream exceptions are hidden). 19 | // 20 | is.exceptions(std::ios_base::badbit); 21 | // 22 | // Main loop 23 | // 24 | const std::streamsize buff_size = 1 << 16; 25 | char * buff = new char [buff_size]; 26 | while (true) 27 | { 28 | is.read(buff, buff_size); 29 | std::streamsize cnt = is.gcount(); 30 | if (cnt == 0) break; 31 | std::cout.write(buff, cnt); 32 | } 33 | delete [] buff; 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Matei David, Ontario Institute for Cancer Research 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: 2 | MAKEFLAGS += -r 3 | SHELL := /bin/bash 4 | .DELETE_ON_ERROR: 5 | .PHONY: all test clean 6 | 7 | all: test-strict_fstream ztxtpipe zpipe zc 8 | 9 | %: %.cpp 10 | g++ -std=c++11 -O0 -g3 -ggdb -fno-eliminate-unused-debug-types -Wall -Wextra -pedantic -I../src -o $@ $^ -lz 11 | 12 | test: ztxtpipe zpipe zc 13 | diff -q ztxtpipe.cpp <(./ztxtpipe 2 | #include 3 | #include 4 | #include "strict_fstream.hpp" 5 | 6 | template < typename Stream_Type > 7 | void test_open(const std::string& stream_class, const std::string& stream_type, 8 | const std::string& filename, int mode, bool set_fail_bit) 9 | { 10 | Stream_Type * s_p = new Stream_Type(); 11 | if (set_fail_bit) 12 | { 13 | s_p->exceptions(std::ios_base::failbit); 14 | } 15 | bool exception_thrown = true; 16 | try 17 | { 18 | s_p->open(filename, static_cast< std::ios_base::openmode >(mode)); 19 | exception_thrown = false; 20 | } 21 | catch (std::exception) {} 22 | std::cout << stream_class << " " << stream_type << " " << (set_fail_bit? "failbit" : "nofailbit") << " " 23 | << strict_fstream::detail::static_method_holder::mode_to_string( 24 | static_cast< std::ios_base::openmode >(mode)) 25 | << " " << (exception_thrown? "1" : "0") << std::endl; 26 | delete s_p; 27 | } 28 | 29 | int main(int argc, char * argv[]) 30 | { 31 | if (argc != 2) 32 | { 33 | std::cerr 34 | << "Use: " << argv[0] << " file" << std::endl 35 | << "Synopsis: Open `file` as a file stream object" << std::endl 36 | << "Stream Classes:" << std::endl 37 | << " std" << std::endl 38 | << " std_mask" << std::endl 39 | << " strict_fstream" << std::endl 40 | << "Stream Types:" << std::endl 41 | << " ifstream" << std::endl 42 | << " ofstream" << std::endl 43 | << " fstream" << std::endl 44 | << "Modes:" << std::endl 45 | << " in=" << std::ios_base::in << std::endl 46 | << " out=" << std::ios_base::out << std::endl 47 | << " app=" << std::ios_base::app << std::endl 48 | << " ate=" << std::ios_base::ate << std::endl 49 | << " trunc=" << std::ios_base::trunc << std::endl 50 | << " binary=" << std::ios_base::binary << std::endl; 51 | std::exit(EXIT_FAILURE); 52 | } 53 | std::vector< int > in_mode_v = { 54 | 0, 55 | std::ios_base::in 56 | }; 57 | std::vector< int > out_mode_v = { 58 | 0, 59 | std::ios_base::out, 60 | std::ios_base::out | std::ios_base::app, 61 | std::ios_base::out | std::ios_base::trunc 62 | }; 63 | std::vector< int > alt_mode_v = { 64 | 0, 65 | std::ios_base::binary, 66 | std::ios_base::ate, 67 | std::ios_base::binary | std::ios_base::ate 68 | }; 69 | for (const auto& in_mode : in_mode_v) 70 | for (const auto& out_mode : out_mode_v) 71 | //for (const auto& alt_mode : alt_mode_v) 72 | { 73 | int mode = in_mode | out_mode; // | alt_mode; 74 | //test_open< std::ifstream >("std", "ifstream", argv[1], mode, false); 75 | //test_open< std::ofstream >("std", "ofstream", argv[1], mode, false); 76 | //test_open< std::fstream >("std", "fstream", argv[1], mode, false); 77 | test_open< std::ifstream >("std", "ifstream", argv[1], mode, true); 78 | test_open< std::ofstream >("std", "ofstream", argv[1], mode, true); 79 | test_open< std::fstream >("std", "fstream", argv[1], mode, true); 80 | test_open< strict_fstream::ifstream >("strict_fstream", "ifstream", argv[1], mode, false); 81 | test_open< strict_fstream::ofstream >("strict_fstream", "ofstream", argv[1], mode, false); 82 | test_open< strict_fstream::fstream >("strict_fstream", "fstream", argv[1], mode, false); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/zc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "zstr.hpp" 6 | 7 | void usage(std::ostream& os, const std::string& prog_name) 8 | { 9 | os << "Use: " << prog_name << " [-c] [-o output_file] files..." << std::endl 10 | << "Synposis:" << std::endl 11 | << " Decompress (with `-c`, compress) files to stdout (with `-o`, to output_file)." << std::endl; 12 | } 13 | 14 | void cat_stream(std::istream& is, std::ostream& os) 15 | { 16 | const std::streamsize buff_size = 1 << 16; 17 | char * buff = new char [buff_size]; 18 | while (true) 19 | { 20 | is.read(buff, buff_size); 21 | std::streamsize cnt = is.gcount(); 22 | if (cnt == 0) break; 23 | os.write(buff, cnt); 24 | } 25 | delete [] buff; 26 | } // cat_stream 27 | 28 | void decompress_files(const std::vector< std::string >& file_v, const std::string& output_file) 29 | { 30 | // 31 | // Set up sink ostream 32 | // 33 | std::unique_ptr< std::ofstream > ofs_p; 34 | std::ostream * os_p = &std::cout; 35 | if (not output_file.empty()) 36 | { 37 | ofs_p = std::unique_ptr< std::ofstream >(new strict_fstream::ofstream(output_file)); 38 | os_p = ofs_p.get(); 39 | } 40 | // 41 | // Process files 42 | // 43 | for (const auto& f : file_v) 44 | { 45 | // 46 | // If `f` is a file, create a zstr::ifstream, else (it is stdin) create a zstr::istream wrapper 47 | // 48 | std::unique_ptr< std::istream > is_p = 49 | (f != "-" 50 | ? std::unique_ptr< std::istream >(new zstr::ifstream(f)) 51 | : std::unique_ptr< std::istream >(new zstr::istream(std::cin))); 52 | // 53 | // Cat stream 54 | // 55 | cat_stream(*is_p, *os_p); 56 | } 57 | } // decompress_files 58 | 59 | void compress_files(const std::vector< std::string >& file_v, const std::string& output_file) 60 | { 61 | // 62 | // Set up compression sink ostream 63 | // 64 | std::unique_ptr< std::ostream > os_p = 65 | (not output_file.empty() 66 | ? std::unique_ptr< std::ostream >(new zstr::ofstream(output_file)) 67 | : std::unique_ptr< std::ostream >(new zstr::ostream(std::cout))); 68 | // 69 | // Process files 70 | // 71 | for (const auto& f : file_v) 72 | { 73 | // 74 | // If `f` is a file, create an ifstream, else read stdin 75 | // 76 | std::unique_ptr< std::ifstream > ifs_p; 77 | std::istream * is_p = &std::cin; 78 | if (f != "-") 79 | { 80 | ifs_p = std::unique_ptr< std::ifstream >(new strict_fstream::ifstream(f)); 81 | is_p = ifs_p.get(); 82 | } 83 | // 84 | // Cat stream 85 | // 86 | cat_stream(*is_p, *os_p); 87 | } 88 | } // compress_files 89 | 90 | int main(int argc, char * argv[]) 91 | { 92 | bool compress = false; 93 | std::string output_file; 94 | int c; 95 | while ((c = getopt(argc, argv, "co:h?")) != -1) 96 | { 97 | switch (c) 98 | { 99 | case 'c': 100 | compress = true; 101 | break; 102 | case 'o': 103 | if (std::string("-") != optarg) 104 | { 105 | output_file = optarg; 106 | } 107 | break; 108 | case '?': 109 | case 'h': 110 | usage(std::cout, argv[0]); 111 | std::exit(EXIT_SUCCESS); 112 | break; 113 | default: 114 | usage(std::cerr, argv[0]); 115 | std::exit(EXIT_FAILURE); 116 | } 117 | } 118 | // 119 | // Gather files to process 120 | // 121 | std::vector< std::string > file_v(&argv[optind], &argv[argc]); 122 | // 123 | // With no other arguments, process stdin 124 | // 125 | if (file_v.empty()) file_v.push_back("-"); 126 | // 127 | // Perform compression/decompression 128 | // 129 | if (compress) 130 | { 131 | compress_files(file_v, output_file); 132 | } 133 | else 134 | { 135 | decompress_files(file_v, output_file); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/strict_fstream.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __STRICT_FSTREAM_HPP 2 | #define __STRICT_FSTREAM_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * This namespace defines wrappers for std::ifstream, std::ofstream, and 11 | * std::fstream objects. The wrappers perform the following steps: 12 | * - check the open modes make sense 13 | * - check that the call to open() is successful 14 | * - (for input streams) check that the opened file is peek-able 15 | * - turn on the badbit in the exception mask 16 | */ 17 | namespace strict_fstream 18 | { 19 | 20 | /// Overload of error-reporting function, to enable use with VS. 21 | /// Ref: http://stackoverflow.com/a/901316/717706 22 | static std::string strerror() 23 | { 24 | std::string buff(80, '\0'); 25 | #ifdef _WIN32 26 | if (strerror_s(&buff[0], buff.size(), errno) != 0) 27 | { 28 | buff = "Unknown error"; 29 | } 30 | #elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE || defined(__APPLE__) 31 | // XSI-compliant strerror_r() 32 | if (strerror_r(errno, &buff[0], buff.size()) != 0) 33 | { 34 | buff = "Unknown error"; 35 | } 36 | #else 37 | // GNU-specific strerror_r() 38 | auto p = strerror_r(errno, &buff[0], buff.size()); 39 | std::string tmp(p, std::strlen(p)); 40 | std::swap(buff, tmp); 41 | #endif 42 | buff.resize(buff.find('\0')); 43 | return buff; 44 | } 45 | 46 | /// Exception class thrown by failed operations. 47 | class Exception 48 | : public std::exception 49 | { 50 | public: 51 | Exception(const std::string& msg) : _msg(msg) {} 52 | const char * what() const noexcept { return _msg.c_str(); } 53 | private: 54 | std::string _msg; 55 | }; // class Exception 56 | 57 | namespace detail 58 | { 59 | 60 | struct static_method_holder 61 | { 62 | static std::string mode_to_string(std::ios_base::openmode mode) 63 | { 64 | static const int n_modes = 6; 65 | static const std::ios_base::openmode mode_val_v[n_modes] = 66 | { 67 | std::ios_base::in, 68 | std::ios_base::out, 69 | std::ios_base::app, 70 | std::ios_base::ate, 71 | std::ios_base::trunc, 72 | std::ios_base::binary 73 | }; 74 | 75 | static const char * mode_name_v[n_modes] = 76 | { 77 | "in", 78 | "out", 79 | "app", 80 | "ate", 81 | "trunc", 82 | "binary" 83 | }; 84 | std::string res; 85 | for (int i = 0; i < n_modes; ++i) 86 | { 87 | if (mode & mode_val_v[i]) 88 | { 89 | res += (! res.empty()? "|" : ""); 90 | res += mode_name_v[i]; 91 | } 92 | } 93 | if (res.empty()) res = "none"; 94 | return res; 95 | } 96 | static void check_mode(const std::string& filename, std::ios_base::openmode mode) 97 | { 98 | if ((mode & std::ios_base::trunc) && ! (mode & std::ios_base::out)) 99 | { 100 | throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: trunc and not out"); 101 | } 102 | else if ((mode & std::ios_base::app) && ! (mode & std::ios_base::out)) 103 | { 104 | throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: app and not out"); 105 | } 106 | else if ((mode & std::ios_base::trunc) && (mode & std::ios_base::app)) 107 | { 108 | throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: trunc and app"); 109 | } 110 | } 111 | static void check_open(std::ios * s_p, const std::string& filename, std::ios_base::openmode mode) 112 | { 113 | if (s_p->fail()) 114 | { 115 | throw Exception(std::string("strict_fstream: open('") 116 | + filename + "'," + mode_to_string(mode) + "): open failed: " 117 | + strerror()); 118 | } 119 | } 120 | static void check_peek(std::istream * is_p, const std::string& filename, std::ios_base::openmode mode) 121 | { 122 | bool peek_failed = true; 123 | try 124 | { 125 | is_p->peek(); 126 | peek_failed = is_p->fail(); 127 | } 128 | catch (std::ios_base::failure e) {} 129 | if (peek_failed) 130 | { 131 | throw Exception(std::string("strict_fstream: open('") 132 | + filename + "'," + mode_to_string(mode) + "): peek failed: " 133 | + strerror()); 134 | } 135 | is_p->clear(); 136 | } 137 | }; // struct static_method_holder 138 | 139 | } // namespace detail 140 | 141 | class ifstream 142 | : public std::ifstream 143 | { 144 | public: 145 | ifstream() = default; 146 | ifstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 147 | { 148 | open(filename, mode); 149 | } 150 | void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 151 | { 152 | mode |= std::ios_base::in; 153 | exceptions(std::ios_base::badbit); 154 | detail::static_method_holder::check_mode(filename, mode); 155 | std::ifstream::open(filename, mode); 156 | detail::static_method_holder::check_open(this, filename, mode); 157 | detail::static_method_holder::check_peek(this, filename, mode); 158 | } 159 | }; // class ifstream 160 | 161 | class ofstream 162 | : public std::ofstream 163 | { 164 | public: 165 | ofstream() = default; 166 | ofstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out) 167 | { 168 | open(filename, mode); 169 | } 170 | void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out) 171 | { 172 | mode |= std::ios_base::out; 173 | exceptions(std::ios_base::badbit); 174 | detail::static_method_holder::check_mode(filename, mode); 175 | std::ofstream::open(filename, mode); 176 | detail::static_method_holder::check_open(this, filename, mode); 177 | } 178 | }; // class ofstream 179 | 180 | class fstream 181 | : public std::fstream 182 | { 183 | public: 184 | fstream() = default; 185 | fstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 186 | { 187 | open(filename, mode); 188 | } 189 | void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 190 | { 191 | if (! (mode & std::ios_base::out)) mode |= std::ios_base::in; 192 | exceptions(std::ios_base::badbit); 193 | detail::static_method_holder::check_mode(filename, mode); 194 | std::fstream::open(filename, mode); 195 | detail::static_method_holder::check_open(this, filename, mode); 196 | detail::static_method_holder::check_peek(this, filename, mode); 197 | } 198 | }; // class fstream 199 | 200 | } // namespace strict_fstream 201 | 202 | #endif 203 | -------------------------------------------------------------------------------- /src/zstr.hpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------- 2 | // Copyright 2015 Ontario Institute for Cancer Research 3 | // Written by Matei David (matei@cs.toronto.edu) 4 | //--------------------------------------------------------- 5 | 6 | // Reference: 7 | // http://stackoverflow.com/questions/14086417/how-to-write-custom-input-stream-in-c 8 | 9 | #ifndef __ZSTR_HPP 10 | #define __ZSTR_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "strict_fstream.hpp" 17 | 18 | namespace zstr 19 | { 20 | 21 | /// Exception class thrown by failed zlib operations. 22 | class Exception 23 | : public std::exception 24 | { 25 | public: 26 | Exception(z_stream * zstrm_p, int ret) 27 | : _msg("zlib: ") 28 | { 29 | switch (ret) 30 | { 31 | case Z_STREAM_ERROR: 32 | _msg += "Z_STREAM_ERROR: "; 33 | break; 34 | case Z_DATA_ERROR: 35 | _msg += "Z_DATA_ERROR: "; 36 | break; 37 | case Z_MEM_ERROR: 38 | _msg += "Z_MEM_ERROR: "; 39 | break; 40 | case Z_VERSION_ERROR: 41 | _msg += "Z_VERSION_ERROR: "; 42 | break; 43 | case Z_BUF_ERROR: 44 | _msg += "Z_BUF_ERROR: "; 45 | break; 46 | default: 47 | std::ostringstream oss; 48 | oss << ret; 49 | _msg += "[" + oss.str() + "]: "; 50 | break; 51 | } 52 | _msg += zstrm_p->msg; 53 | } 54 | Exception(const std::string msg) : _msg(msg) {} 55 | const char * what() const noexcept { return _msg.c_str(); } 56 | private: 57 | std::string _msg; 58 | }; // class Exception 59 | 60 | namespace detail 61 | { 62 | 63 | class z_stream_wrapper 64 | : public z_stream 65 | { 66 | public: 67 | z_stream_wrapper(bool _is_input = true, int _level = Z_DEFAULT_COMPRESSION) 68 | : is_input(_is_input) 69 | { 70 | this->zalloc = Z_NULL; 71 | this->zfree = Z_NULL; 72 | this->opaque = Z_NULL; 73 | int ret; 74 | if (is_input) 75 | { 76 | this->avail_in = 0; 77 | this->next_in = Z_NULL; 78 | ret = inflateInit2(this, 15+32); 79 | } 80 | else 81 | { 82 | ret = deflateInit2(this, _level, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY); 83 | } 84 | if (ret != Z_OK) throw Exception(this, ret); 85 | } 86 | ~z_stream_wrapper() 87 | { 88 | if (is_input) 89 | { 90 | inflateEnd(this); 91 | } 92 | else 93 | { 94 | deflateEnd(this); 95 | } 96 | } 97 | private: 98 | bool is_input; 99 | }; // class z_stream_wrapper 100 | 101 | } // namespace detail 102 | 103 | class istreambuf 104 | : public std::streambuf 105 | { 106 | public: 107 | istreambuf(std::streambuf * _sbuf_p, 108 | std::size_t _buff_size = default_buff_size, bool _auto_detect = true) 109 | : sbuf_p(_sbuf_p), 110 | zstrm_p(nullptr), 111 | buff_size(_buff_size), 112 | auto_detect(_auto_detect), 113 | auto_detect_run(false), 114 | is_text(false) 115 | { 116 | assert(sbuf_p); 117 | in_buff = new char [buff_size]; 118 | in_buff_start = in_buff; 119 | in_buff_end = in_buff; 120 | out_buff = new char [buff_size]; 121 | setg(out_buff, out_buff, out_buff); 122 | } 123 | 124 | istreambuf(const istreambuf &) = delete; 125 | istreambuf(istreambuf &&) = default; 126 | istreambuf & operator = (const istreambuf &) = delete; 127 | istreambuf & operator = (istreambuf &&) = default; 128 | 129 | virtual ~istreambuf() 130 | { 131 | delete [] in_buff; 132 | delete [] out_buff; 133 | if (zstrm_p) delete zstrm_p; 134 | } 135 | 136 | virtual std::streambuf::int_type underflow() 137 | { 138 | if (this->gptr() == this->egptr()) 139 | { 140 | // pointers for free region in output buffer 141 | char * out_buff_free_start = out_buff; 142 | do 143 | { 144 | // read more input if none available 145 | if (in_buff_start == in_buff_end) 146 | { 147 | // empty input buffer: refill from the start 148 | in_buff_start = in_buff; 149 | std::streamsize sz = sbuf_p->sgetn(in_buff, buff_size); 150 | in_buff_end = in_buff + sz; 151 | if (in_buff_end == in_buff_start) break; // end of input 152 | } 153 | // auto detect if the stream contains text or deflate data 154 | if (auto_detect && ! auto_detect_run) 155 | { 156 | auto_detect_run = true; 157 | unsigned char b0 = *reinterpret_cast< unsigned char * >(in_buff_start); 158 | unsigned char b1 = *reinterpret_cast< unsigned char * >(in_buff_start + 1); 159 | // Ref: 160 | // http://en.wikipedia.org/wiki/Gzip 161 | // http://stackoverflow.com/questions/9050260/what-does-a-zlib-header-look-like 162 | is_text = ! (in_buff_start + 2 <= in_buff_end 163 | && ((b0 == 0x1F && b1 == 0x8B) // gzip header 164 | || (b0 == 0x78 && (b1 == 0x01 // zlib header 165 | || b1 == 0x9C 166 | || b1 == 0xDA)))); 167 | } 168 | if (is_text) 169 | { 170 | // simply swap in_buff and out_buff, and adjust pointers 171 | assert(in_buff_start == in_buff); 172 | std::swap(in_buff, out_buff); 173 | out_buff_free_start = in_buff_end; 174 | in_buff_start = in_buff; 175 | in_buff_end = in_buff; 176 | } 177 | else 178 | { 179 | // run inflate() on input 180 | if (! zstrm_p) zstrm_p = new detail::z_stream_wrapper(true); 181 | zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(in_buff_start); 182 | zstrm_p->avail_in = in_buff_end - in_buff_start; 183 | zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff_free_start); 184 | zstrm_p->avail_out = (out_buff + buff_size) - out_buff_free_start; 185 | int ret = inflate(zstrm_p, Z_NO_FLUSH); 186 | // process return code 187 | if (ret != Z_OK && ret != Z_STREAM_END) throw Exception(zstrm_p, ret); 188 | // update in&out pointers following inflate() 189 | in_buff_start = reinterpret_cast< decltype(in_buff_start) >(zstrm_p->next_in); 190 | in_buff_end = in_buff_start + zstrm_p->avail_in; 191 | out_buff_free_start = reinterpret_cast< decltype(out_buff_free_start) >(zstrm_p->next_out); 192 | assert(out_buff_free_start + zstrm_p->avail_out == out_buff + buff_size); 193 | // if stream ended, deallocate inflator 194 | if (ret == Z_STREAM_END) 195 | { 196 | delete zstrm_p; 197 | zstrm_p = nullptr; 198 | } 199 | } 200 | } while (out_buff_free_start == out_buff); 201 | // 2 exit conditions: 202 | // - end of input: there might or might not be output available 203 | // - out_buff_free_start != out_buff: output available 204 | this->setg(out_buff, out_buff, out_buff_free_start); 205 | } 206 | return this->gptr() == this->egptr() 207 | ? traits_type::eof() 208 | : traits_type::to_int_type(*this->gptr()); 209 | } 210 | private: 211 | std::streambuf * sbuf_p; 212 | char * in_buff; 213 | char * in_buff_start; 214 | char * in_buff_end; 215 | char * out_buff; 216 | detail::z_stream_wrapper * zstrm_p; 217 | std::size_t buff_size; 218 | bool auto_detect; 219 | bool auto_detect_run; 220 | bool is_text; 221 | 222 | static const std::size_t default_buff_size = (std::size_t)1 << 20; 223 | }; // class istreambuf 224 | 225 | class ostreambuf 226 | : public std::streambuf 227 | { 228 | public: 229 | ostreambuf(std::streambuf * _sbuf_p, 230 | std::size_t _buff_size = default_buff_size, int _level = Z_DEFAULT_COMPRESSION) 231 | : sbuf_p(_sbuf_p), 232 | zstrm_p(new detail::z_stream_wrapper(false, _level)), 233 | buff_size(_buff_size) 234 | { 235 | assert(sbuf_p); 236 | in_buff = new char [buff_size]; 237 | out_buff = new char [buff_size]; 238 | setp(in_buff, in_buff + buff_size); 239 | } 240 | 241 | ostreambuf(const ostreambuf &) = delete; 242 | ostreambuf(ostreambuf &&) = default; 243 | ostreambuf & operator = (const ostreambuf &) = delete; 244 | ostreambuf & operator = (ostreambuf &&) = default; 245 | 246 | int deflate_loop(int flush) 247 | { 248 | while (true) 249 | { 250 | zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff); 251 | zstrm_p->avail_out = buff_size; 252 | int ret = deflate(zstrm_p, flush); 253 | if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) throw Exception(zstrm_p, ret); 254 | std::streamsize sz = sbuf_p->sputn(out_buff, reinterpret_cast< decltype(out_buff) >(zstrm_p->next_out) - out_buff); 255 | if (sz != reinterpret_cast< decltype(out_buff) >(zstrm_p->next_out) - out_buff) 256 | { 257 | // there was an error in the sink stream 258 | return -1; 259 | } 260 | if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || sz == 0) 261 | { 262 | break; 263 | } 264 | } 265 | return 0; 266 | } 267 | 268 | virtual ~ostreambuf() 269 | { 270 | // flush the zlib stream 271 | // 272 | // NOTE: Errors here (sync() return value not 0) are ignored, because we 273 | // cannot throw in a destructor. This mirrors the behaviour of 274 | // std::basic_filebuf::~basic_filebuf(). To see an exception on error, 275 | // close the ofstream with an explicit call to close(), and do not rely 276 | // on the implicit call in the destructor. 277 | // 278 | sync(); 279 | delete [] in_buff; 280 | delete [] out_buff; 281 | delete zstrm_p; 282 | } 283 | virtual std::streambuf::int_type overflow(std::streambuf::int_type c = traits_type::eof()) 284 | { 285 | zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(pbase()); 286 | zstrm_p->avail_in = pptr() - pbase(); 287 | while (zstrm_p->avail_in > 0) 288 | { 289 | int r = deflate_loop(Z_NO_FLUSH); 290 | if (r != 0) 291 | { 292 | setp(nullptr, nullptr); 293 | return traits_type::eof(); 294 | } 295 | } 296 | setp(in_buff, in_buff + buff_size); 297 | return traits_type::eq_int_type(c, traits_type::eof()) ? traits_type::eof() : sputc(c); 298 | } 299 | virtual int sync() 300 | { 301 | // first, call overflow to clear in_buff 302 | overflow(); 303 | if (! pptr()) return -1; 304 | // then, call deflate asking to finish the zlib stream 305 | zstrm_p->next_in = nullptr; 306 | zstrm_p->avail_in = 0; 307 | if (deflate_loop(Z_FINISH) != 0) return -1; 308 | deflateReset(zstrm_p); 309 | return 0; 310 | } 311 | private: 312 | std::streambuf * sbuf_p; 313 | char * in_buff; 314 | char * out_buff; 315 | detail::z_stream_wrapper * zstrm_p; 316 | std::size_t buff_size; 317 | 318 | static const std::size_t default_buff_size = (std::size_t)1 << 20; 319 | }; // class ostreambuf 320 | 321 | class istream 322 | : public std::istream 323 | { 324 | public: 325 | istream(std::istream & is) 326 | : std::istream(new istreambuf(is.rdbuf())) 327 | { 328 | exceptions(std::ios_base::badbit); 329 | } 330 | explicit istream(std::streambuf * sbuf_p) 331 | : std::istream(new istreambuf(sbuf_p)) 332 | { 333 | exceptions(std::ios_base::badbit); 334 | } 335 | virtual ~istream() 336 | { 337 | delete rdbuf(); 338 | } 339 | }; // class istream 340 | 341 | class ostream 342 | : public std::ostream 343 | { 344 | public: 345 | ostream(std::ostream & os) 346 | : std::ostream(new ostreambuf(os.rdbuf())) 347 | { 348 | exceptions(std::ios_base::badbit); 349 | } 350 | explicit ostream(std::streambuf * sbuf_p) 351 | : std::ostream(new ostreambuf(sbuf_p)) 352 | { 353 | exceptions(std::ios_base::badbit); 354 | } 355 | virtual ~ostream() 356 | { 357 | delete rdbuf(); 358 | } 359 | }; // class ostream 360 | 361 | namespace detail 362 | { 363 | 364 | template < typename FStream_Type > 365 | struct strict_fstream_holder 366 | { 367 | strict_fstream_holder(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 368 | : _fs(filename, mode) 369 | {} 370 | FStream_Type _fs; 371 | }; // class strict_fstream_holder 372 | 373 | } // namespace detail 374 | 375 | class ifstream 376 | : private detail::strict_fstream_holder< strict_fstream::ifstream >, 377 | public std::istream 378 | { 379 | public: 380 | explicit ifstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 381 | : detail::strict_fstream_holder< strict_fstream::ifstream >(filename, mode), 382 | std::istream(new istreambuf(_fs.rdbuf())) 383 | { 384 | exceptions(std::ios_base::badbit); 385 | } 386 | virtual ~ifstream() 387 | { 388 | if (rdbuf()) delete rdbuf(); 389 | } 390 | }; // class ifstream 391 | 392 | class ofstream 393 | : private detail::strict_fstream_holder< strict_fstream::ofstream >, 394 | public std::ostream 395 | { 396 | public: 397 | explicit ofstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out) 398 | : detail::strict_fstream_holder< strict_fstream::ofstream >(filename, mode | std::ios_base::binary), 399 | std::ostream(new ostreambuf(_fs.rdbuf())) 400 | { 401 | exceptions(std::ios_base::badbit); 402 | } 403 | virtual ~ofstream() 404 | { 405 | if (rdbuf()) delete rdbuf(); 406 | } 407 | }; // class ofstream 408 | 409 | } // namespace zstr 410 | 411 | #endif 412 | --------------------------------------------------------------------------------