1 /*
2 * compress_zlib.c: zlib data compression routines
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25 #include <zlib.h>
26
27 #include "private/svn_subr_private.h"
28 #include "private/svn_error_private.h"
29
30 #include "svn_private_config.h"
31
32 const char *
svn_zlib__compiled_version(void)33 svn_zlib__compiled_version(void)
34 {
35 static const char zlib_version_str[] = ZLIB_VERSION;
36
37 return zlib_version_str;
38 }
39
40 const char *
svn_zlib__runtime_version(void)41 svn_zlib__runtime_version(void)
42 {
43 return zlibVersion();
44 }
45
46
47 /* The zlib compressBound function was not exported until 1.2.0. */
48 #if ZLIB_VERNUM >= 0x1200
49 #define svnCompressBound(LEN) compressBound(LEN)
50 #else
51 #define svnCompressBound(LEN) ((LEN) + ((LEN) >> 12) + ((LEN) >> 14) + 11)
52 #endif
53
54 /* For svndiff1, address/instruction/new data under this size will not
55 be compressed using zlib as a secondary compressor. */
56 #define MIN_COMPRESS_SIZE 512
57
58 /* If IN is a string that is >= MIN_COMPRESS_SIZE and the COMPRESSION_LEVEL
59 is not SVN_DELTA_COMPRESSION_LEVEL_NONE, zlib compress it and places the
60 result in OUT, with an integer prepended specifying the original size.
61 If IN is < MIN_COMPRESS_SIZE, or if the compressed version of IN was no
62 smaller than the original IN, OUT will be a copy of IN with the size
63 prepended as an integer. */
64 static svn_error_t *
zlib_encode(const char * data,apr_size_t len,svn_stringbuf_t * out,int compression_level)65 zlib_encode(const char *data,
66 apr_size_t len,
67 svn_stringbuf_t *out,
68 int compression_level)
69 {
70 unsigned long endlen;
71 apr_size_t intlen;
72 unsigned char buf[SVN__MAX_ENCODED_UINT_LEN], *p;
73
74 svn_stringbuf_setempty(out);
75 p = svn__encode_uint(buf, (apr_uint64_t)len);
76 svn_stringbuf_appendbytes(out, (const char *)buf, p - buf);
77
78 intlen = out->len;
79
80 /* Compression initialization overhead is considered to large for
81 short buffers. Also, if we don't actually want to compress data,
82 ZLIB will produce an output no shorter than the input. Hence,
83 the DATA would directly appended to OUT, so we can do that directly
84 without calling ZLIB before. */
85 if (len < MIN_COMPRESS_SIZE || compression_level == SVN__COMPRESSION_NONE)
86 {
87 svn_stringbuf_appendbytes(out, data, len);
88 }
89 else
90 {
91 int zerr;
92
93 svn_stringbuf_ensure(out, svnCompressBound(len) + intlen);
94 endlen = out->blocksize;
95
96 zerr = compress2((unsigned char *)out->data + intlen, &endlen,
97 (const unsigned char *)data, len,
98 compression_level);
99 if (zerr != Z_OK)
100 return svn_error_trace(svn_error__wrap_zlib(
101 zerr, "compress2",
102 _("Compression of svndiff data failed")));
103
104 /* Compression didn't help :(, just append the original text */
105 if (endlen >= len)
106 {
107 svn_stringbuf_appendbytes(out, data, len);
108 return SVN_NO_ERROR;
109 }
110 out->len = endlen + intlen;
111 out->data[out->len] = 0;
112 }
113 return SVN_NO_ERROR;
114 }
115
116 /* Decode the possibly-zlib compressed string of length INLEN that is in
117 IN, into OUT. We expect an integer is prepended to IN that specifies
118 the original size, and that if encoded size == original size, that the
119 remaining data is not compressed.
120 In that case, we will simply return pointer into IN as data pointer for
121 OUT, COPYLESS_ALLOWED has been set. The, the caller is expected not to
122 modify the contents of OUT.
123 An error is returned if the decoded length exceeds the given LIMIT.
124 */
125 static svn_error_t *
zlib_decode(const unsigned char * in,apr_size_t inLen,svn_stringbuf_t * out,apr_size_t limit)126 zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out,
127 apr_size_t limit)
128 {
129 apr_size_t len;
130 apr_uint64_t size;
131 const unsigned char *oldplace = in;
132
133 /* First thing in the string is the original length. */
134 in = svn__decode_uint(&size, in, in + inLen);
135 len = (apr_size_t)size;
136 if (in == NULL || len != size)
137 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL,
138 _("Decompression of zlib compressed data failed: no size"));
139 if (len > limit)
140 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL,
141 _("Decompression of zlib compressed data failed: "
142 "size too large"));
143
144 /* We need to subtract the size of the encoded original length off the
145 * still remaining input length. */
146 inLen -= (in - oldplace);
147 if (inLen == len)
148 {
149 svn_stringbuf_ensure(out, len);
150 memcpy(out->data, in, len);
151 out->data[len] = 0;
152 out->len = len;
153
154 return SVN_NO_ERROR;
155 }
156 else
157 {
158 unsigned long zlen = len;
159 int zerr;
160
161 svn_stringbuf_ensure(out, len);
162 zerr = uncompress((unsigned char *)out->data, &zlen, in, inLen);
163 if (zerr != Z_OK)
164 return svn_error_trace(svn_error__wrap_zlib(
165 zerr, "uncompress",
166 _("Decompression of svndiff data failed")));
167
168 /* Zlib should not produce something that has a different size than the
169 original length we stored. */
170 if (zlen != len)
171 return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
172 NULL,
173 _("Size of uncompressed data "
174 "does not match stored original length"));
175 out->data[zlen] = 0;
176 out->len = zlen;
177 }
178 return SVN_NO_ERROR;
179 }
180
181 svn_error_t *
svn__compress_zlib(const void * data,apr_size_t len,svn_stringbuf_t * out,int compression_method)182 svn__compress_zlib(const void *data, apr_size_t len,
183 svn_stringbuf_t *out,
184 int compression_method)
185 {
186 if ( compression_method < SVN__COMPRESSION_NONE
187 || compression_method > SVN__COMPRESSION_ZLIB_MAX)
188 return svn_error_createf(SVN_ERR_BAD_COMPRESSION_METHOD, NULL,
189 _("Unsupported compression method %d"),
190 compression_method);
191
192 return zlib_encode(data, len, out, compression_method);
193 }
194
195 svn_error_t *
svn__decompress_zlib(const void * data,apr_size_t len,svn_stringbuf_t * out,apr_size_t limit)196 svn__decompress_zlib(const void *data, apr_size_t len,
197 svn_stringbuf_t *out,
198 apr_size_t limit)
199 {
200 return zlib_decode(data, len, out, limit);
201 }
202