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