1 /** 2 * Compress/decompress data using the zlib library. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.utils.zlib; 15 16 import etc.c.zlib; 17 import std.conv; 18 import std.exception; 19 20 import ae.sys.data; 21 22 class ZlibException : Exception 23 { 24 private static string getmsg(int err) nothrow @nogc pure @safe 25 { 26 switch (err) 27 { 28 case Z_STREAM_END: return "stream end"; 29 case Z_NEED_DICT: return "need dict"; 30 case Z_ERRNO: return "errno"; 31 case Z_STREAM_ERROR: return "stream error"; 32 case Z_DATA_ERROR: return "data error"; 33 case Z_MEM_ERROR: return "mem error"; 34 case Z_BUF_ERROR: return "buf error"; 35 case Z_VERSION_ERROR: return "version error"; 36 default: return "unknown error"; 37 } 38 } 39 40 this(int err, z_stream* zs) 41 { 42 if (zs.msg) 43 super(to!string(zs.msg)); 44 else 45 super(getmsg(err)); 46 } 47 48 this(string msg) { super(msg); } 49 } 50 51 enum ZlibMode { normal, raw, gzipOnly, gzipAuto } 52 53 struct ZlibOptions 54 { 55 int deflateLevel = Z_DEFAULT_COMPRESSION; 56 int windowBits = 15; /// 8..15 - actual windowBits, without additional meaning 57 ZlibMode mode; 58 59 invariant() 60 { 61 assert(deflateLevel == Z_DEFAULT_COMPRESSION || (deflateLevel >= 0 && deflateLevel <= 9)); 62 assert(windowBits >= 8 && windowBits <= 15); 63 } 64 65 private: 66 @property 67 int zwindowBits() 68 { 69 final switch (mode) 70 { 71 case ZlibMode.normal: 72 return windowBits; 73 case ZlibMode.raw: 74 return -windowBits; 75 case ZlibMode.gzipOnly: 76 return 16+windowBits; 77 case ZlibMode.gzipAuto: 78 return 32+windowBits; 79 } 80 } 81 } 82 83 struct ZlibProcess(bool COMPRESSING) 84 { 85 void init(ZlibOptions options = ZlibOptions.init) 86 { 87 static if (COMPRESSING) 88 //zenforce(deflateInit(&zs, options.deflateLevel)); 89 zenforce(deflateInit2(&zs, options.deflateLevel, Z_DEFLATED, options.zwindowBits, 8, Z_DEFAULT_STRATEGY)); 90 else 91 //zenforce(inflateInit(&zs)); 92 zenforce(inflateInit2(&zs, options.zwindowBits)); 93 } 94 95 void processChunk(Data chunk) 96 { 97 if (!chunk.length) 98 return; 99 100 assert(zs.avail_in == 0); 101 zs.next_in = cast(ubyte*) chunk.ptr; 102 zs.avail_in = to!uint(chunk.length); 103 104 do 105 { 106 if (zs.avail_out == 0) 107 allocChunk(adjustSize(zs.avail_in)); 108 109 assert(zs.avail_in && zs.next_in ); 110 assert(zs.avail_out && zs.next_out); 111 if (zend(processFunc(&zs, Z_NO_FLUSH))) 112 enforce(zs.avail_in==0, new ZlibException("Trailing data")); 113 } while (zs.avail_in); 114 } 115 116 Data[] flush() 117 { 118 if (zs.avail_out == 0) 119 allocChunk(adjustSize(zs.avail_in)); 120 121 while (!zend(processFunc(&zs, Z_FINISH))) 122 allocChunk(zs.avail_out*2+1); 123 124 saveChunk(); 125 return outputChunks; 126 } 127 128 static Data[] process(Data[] input, ZlibOptions options = ZlibOptions.init) 129 { 130 typeof(this) zp; 131 zp.init(options); 132 foreach (ref chunk; input) 133 zp.processChunk(chunk); 134 return zp.flush(); 135 } 136 137 static Data process(Data input, ZlibOptions options = ZlibOptions.init) 138 { 139 return process([input], options).joinData(); 140 } 141 142 ~this() 143 { 144 zenforce(endFunc(&zs)); 145 } 146 147 private: 148 z_stream zs; 149 Data currentChunk; 150 Data[] outputChunks; 151 152 static if (COMPRESSING) 153 { 154 alias deflate processFunc; 155 alias deflateEnd endFunc; 156 157 size_t adjustSize(size_t sz) { return sz / 4 + 1; } 158 } 159 else 160 { 161 alias inflate processFunc; 162 alias inflateEnd endFunc; 163 164 size_t adjustSize(size_t sz) { return sz * 4 + 1; } 165 } 166 167 void zenforce(int ret) 168 { 169 if (ret != Z_OK) 170 throw new ZlibException(ret, &zs); 171 } 172 173 bool zend(int ret) 174 { 175 if (ret == Z_STREAM_END) 176 return true; 177 zenforce(ret); 178 return false; 179 } 180 181 void saveChunk() 182 { 183 if (zs.next_out && zs.next_out != currentChunk.ptr) 184 { 185 outputChunks ~= currentChunk[0..zs.next_out-cast(ubyte*)currentChunk.ptr]; 186 currentChunk = Data(); 187 } 188 zs.next_out = null; 189 } 190 191 void allocChunk(size_t sz) 192 { 193 saveChunk(); 194 currentChunk = Data(sz); 195 currentChunk.length = currentChunk.capacity; 196 zs.next_out = cast(ubyte*)currentChunk.mptr; 197 zs.avail_out = to!uint(currentChunk.length); 198 } 199 } 200 201 alias ZlibProcess!true ZlibDeflater; 202 alias ZlibProcess!false ZlibInflater; 203 204 alias ZlibDeflater.process compress; 205 alias ZlibInflater.process uncompress; 206 207 Data compress(Data input, int level) 208 { 209 return compress(input, ZlibOptions(level)); 210 } 211 212 unittest 213 { 214 void testRoundtrip(ubyte[] src) 215 { 216 ubyte[] def = cast(ubyte[]) compress(Data(src)).toHeap; 217 ubyte[] res = cast(ubyte[])uncompress(Data(def)).toHeap; 218 assert(res == src); 219 } 220 221 testRoundtrip(cast(ubyte[]) 222 "the quick brown fox jumps over the lazy dog\r 223 the quick brown fox jumps over the lazy dog\r 224 "); 225 testRoundtrip([0]); 226 testRoundtrip(null); 227 }