/src/libgit2/src/util/zstream.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) the libgit2 contributors. All rights reserved. |
3 | | * |
4 | | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | | * a Linking Exception. For full terms see the included COPYING file. |
6 | | */ |
7 | | |
8 | | #include "zstream.h" |
9 | | |
10 | | #include <zlib.h> |
11 | | |
12 | | #include "str.h" |
13 | | |
14 | 0 | #define ZSTREAM_BUFFER_SIZE (1024 * 1024) |
15 | 0 | #define ZSTREAM_BUFFER_MIN_EXTRA 8 |
16 | | |
17 | | GIT_INLINE(int) zstream_seterr(git_zstream *zs) |
18 | 0 | { |
19 | 0 | switch (zs->zerr) { |
20 | 0 | case Z_OK: |
21 | 0 | case Z_STREAM_END: |
22 | 0 | case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ |
23 | 0 | return 0; |
24 | 0 | case Z_MEM_ERROR: |
25 | 0 | git_error_set_oom(); |
26 | 0 | break; |
27 | 0 | default: |
28 | 0 | if (zs->z.msg) |
29 | 0 | git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg); |
30 | 0 | else |
31 | 0 | git_error_set(GIT_ERROR_ZLIB, "unknown compression error"); |
32 | 0 | } |
33 | | |
34 | 0 | return -1; |
35 | 0 | } |
36 | | |
37 | | int git_zstream_init(git_zstream *zstream, git_zstream_t type) |
38 | 0 | { |
39 | 0 | zstream->type = type; |
40 | |
|
41 | 0 | if (zstream->type == GIT_ZSTREAM_INFLATE) |
42 | 0 | zstream->zerr = inflateInit(&zstream->z); |
43 | 0 | else |
44 | 0 | zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); |
45 | 0 | return zstream_seterr(zstream); |
46 | 0 | } |
47 | | |
48 | | void git_zstream_free(git_zstream *zstream) |
49 | 0 | { |
50 | 0 | if (zstream->type == GIT_ZSTREAM_INFLATE) |
51 | 0 | inflateEnd(&zstream->z); |
52 | 0 | else |
53 | 0 | deflateEnd(&zstream->z); |
54 | 0 | } |
55 | | |
56 | | void git_zstream_reset(git_zstream *zstream) |
57 | 0 | { |
58 | 0 | if (zstream->type == GIT_ZSTREAM_INFLATE) |
59 | 0 | inflateReset(&zstream->z); |
60 | 0 | else |
61 | 0 | deflateReset(&zstream->z); |
62 | 0 | zstream->in = NULL; |
63 | 0 | zstream->in_len = 0; |
64 | 0 | zstream->zerr = Z_STREAM_END; |
65 | 0 | } |
66 | | |
67 | | int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len) |
68 | 0 | { |
69 | 0 | zstream->in = in; |
70 | 0 | zstream->in_len = in_len; |
71 | 0 | zstream->zerr = Z_OK; |
72 | 0 | return 0; |
73 | 0 | } |
74 | | |
75 | | bool git_zstream_done(git_zstream *zstream) |
76 | 0 | { |
77 | 0 | return (!zstream->in_len && zstream->zerr == Z_STREAM_END); |
78 | 0 | } |
79 | | |
80 | | bool git_zstream_eos(git_zstream *zstream) |
81 | 0 | { |
82 | 0 | return zstream->zerr == Z_STREAM_END; |
83 | 0 | } |
84 | | |
85 | | size_t git_zstream_suggest_output_len(git_zstream *zstream) |
86 | 0 | { |
87 | 0 | if (zstream->in_len > ZSTREAM_BUFFER_SIZE) |
88 | 0 | return ZSTREAM_BUFFER_SIZE; |
89 | 0 | else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA) |
90 | 0 | return zstream->in_len; |
91 | 0 | else |
92 | 0 | return ZSTREAM_BUFFER_MIN_EXTRA; |
93 | 0 | } |
94 | | |
95 | | int git_zstream_get_output_chunk( |
96 | | void *out, size_t *out_len, git_zstream *zstream) |
97 | 0 | { |
98 | 0 | size_t in_queued, in_used, out_queued; |
99 | | |
100 | | /* set up input data */ |
101 | 0 | zstream->z.next_in = (Bytef *)zstream->in; |
102 | | |
103 | | /* feed as much data to zlib as it can consume, at most UINT_MAX */ |
104 | 0 | if (zstream->in_len > UINT_MAX) { |
105 | 0 | zstream->z.avail_in = UINT_MAX; |
106 | 0 | zstream->flush = Z_NO_FLUSH; |
107 | 0 | } else { |
108 | 0 | zstream->z.avail_in = (uInt)zstream->in_len; |
109 | 0 | zstream->flush = Z_FINISH; |
110 | 0 | } |
111 | 0 | in_queued = (size_t)zstream->z.avail_in; |
112 | | |
113 | | /* set up output data */ |
114 | 0 | zstream->z.next_out = out; |
115 | 0 | zstream->z.avail_out = (uInt)*out_len; |
116 | |
|
117 | 0 | if ((size_t)zstream->z.avail_out != *out_len) |
118 | 0 | zstream->z.avail_out = UINT_MAX; |
119 | 0 | out_queued = (size_t)zstream->z.avail_out; |
120 | | |
121 | | /* compress next chunk */ |
122 | 0 | if (zstream->type == GIT_ZSTREAM_INFLATE) |
123 | 0 | zstream->zerr = inflate(&zstream->z, zstream->flush); |
124 | 0 | else |
125 | 0 | zstream->zerr = deflate(&zstream->z, zstream->flush); |
126 | |
|
127 | 0 | if (zstream_seterr(zstream)) |
128 | 0 | return -1; |
129 | | |
130 | 0 | in_used = (in_queued - zstream->z.avail_in); |
131 | 0 | zstream->in_len -= in_used; |
132 | 0 | zstream->in += in_used; |
133 | |
|
134 | 0 | *out_len = (out_queued - zstream->z.avail_out); |
135 | |
|
136 | 0 | return 0; |
137 | 0 | } |
138 | | |
139 | | int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) |
140 | 0 | { |
141 | 0 | size_t out_remain = *out_len; |
142 | |
|
143 | 0 | if (zstream->in_len && zstream->zerr == Z_STREAM_END) { |
144 | 0 | git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage"); |
145 | 0 | return -1; |
146 | 0 | } |
147 | | |
148 | 0 | while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { |
149 | 0 | size_t out_written = out_remain; |
150 | |
|
151 | 0 | if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) |
152 | 0 | return -1; |
153 | | |
154 | 0 | out_remain -= out_written; |
155 | 0 | out = ((char *)out) + out_written; |
156 | 0 | } |
157 | | |
158 | | /* either we finished the input or we did not flush the data */ |
159 | 0 | GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH); |
160 | | |
161 | | /* set out_size to number of bytes actually written to output */ |
162 | 0 | *out_len = *out_len - out_remain; |
163 | |
|
164 | 0 | return 0; |
165 | 0 | } |
166 | | |
167 | | static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type) |
168 | 0 | { |
169 | 0 | git_zstream zs = GIT_ZSTREAM_INIT; |
170 | 0 | int error = 0; |
171 | |
|
172 | 0 | if ((error = git_zstream_init(&zs, type)) < 0) |
173 | 0 | return error; |
174 | | |
175 | 0 | if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) |
176 | 0 | goto done; |
177 | | |
178 | 0 | while (!git_zstream_done(&zs)) { |
179 | 0 | size_t step = git_zstream_suggest_output_len(&zs), written; |
180 | |
|
181 | 0 | if ((error = git_str_grow_by(out, step)) < 0) |
182 | 0 | goto done; |
183 | | |
184 | 0 | written = out->asize - out->size; |
185 | |
|
186 | 0 | if ((error = git_zstream_get_output( |
187 | 0 | out->ptr + out->size, &written, &zs)) < 0) |
188 | 0 | goto done; |
189 | | |
190 | 0 | out->size += written; |
191 | 0 | } |
192 | | |
193 | | /* NULL terminate for consistency if possible */ |
194 | 0 | if (out->size < out->asize) |
195 | 0 | out->ptr[out->size] = '\0'; |
196 | |
|
197 | 0 | done: |
198 | 0 | git_zstream_free(&zs); |
199 | 0 | return error; |
200 | 0 | } |
201 | | |
202 | | int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len) |
203 | 0 | { |
204 | 0 | return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); |
205 | 0 | } |
206 | | |
207 | | int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len) |
208 | 0 | { |
209 | 0 | return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); |
210 | 0 | } |