summaryrefslogtreecommitdiff
path: root/subversion/libsvn_subr/compress.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_subr/compress.c')
-rw-r--r--subversion/libsvn_subr/compress.c257
1 files changed, 257 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/compress.c b/subversion/libsvn_subr/compress.c
new file mode 100644
index 000000000000..004e443628bd
--- /dev/null
+++ b/subversion/libsvn_subr/compress.c
@@ -0,0 +1,257 @@
+/*
+ * compress.c: various data compression routines
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <string.h>
+#include <assert.h>
+#include <zlib.h>
+
+#include "private/svn_subr_private.h"
+#include "private/svn_error_private.h"
+
+#include "svn_private_config.h"
+
+const char *
+svn_zlib__compiled_version(void)
+{
+ static const char zlib_version_str[] = ZLIB_VERSION;
+
+ return zlib_version_str;
+}
+
+const char *
+svn_zlib__runtime_version(void)
+{
+ return zlibVersion();
+}
+
+
+/* The zlib compressBound function was not exported until 1.2.0. */
+#if ZLIB_VERNUM >= 0x1200
+#define svnCompressBound(LEN) compressBound(LEN)
+#else
+#define svnCompressBound(LEN) ((LEN) + ((LEN) >> 12) + ((LEN) >> 14) + 11)
+#endif
+
+/* For svndiff1, address/instruction/new data under this size will not
+ be compressed using zlib as a secondary compressor. */
+#define MIN_COMPRESS_SIZE 512
+
+unsigned char *
+svn__encode_uint(unsigned char *p, apr_uint64_t val)
+{
+ int n;
+ apr_uint64_t v;
+
+ /* Figure out how many bytes we'll need. */
+ v = val >> 7;
+ n = 1;
+ while (v > 0)
+ {
+ v = v >> 7;
+ n++;
+ }
+
+ /* Encode the remaining bytes; n is always the number of bytes
+ coming after the one we're encoding. */
+ while (--n >= 1)
+ *p++ = (unsigned char)(((val >> (n * 7)) | 0x80) & 0xff);
+
+ *p++ = (unsigned char)(val & 0x7f);
+
+ return p;
+}
+
+const unsigned char *
+svn__decode_uint(apr_uint64_t *val,
+ const unsigned char *p,
+ const unsigned char *end)
+{
+ apr_uint64_t temp = 0;
+
+ if (p + SVN__MAX_ENCODED_UINT_LEN < end)
+ end = p + SVN__MAX_ENCODED_UINT_LEN;
+
+ /* Decode bytes until we're done. */
+ while (SVN__PREDICT_TRUE(p < end))
+ {
+ unsigned int c = *p++;
+
+ if (c < 0x80)
+ {
+ *val = (temp << 7) | c;
+ return p;
+ }
+ else
+ {
+ temp = (temp << 7) | (c & 0x7f);
+ }
+ }
+
+ return NULL;
+}
+
+/* If IN is a string that is >= MIN_COMPRESS_SIZE and the COMPRESSION_LEVEL
+ is not SVN_DELTA_COMPRESSION_LEVEL_NONE, zlib compress it and places the
+ result in OUT, with an integer prepended specifying the original size.
+ If IN is < MIN_COMPRESS_SIZE, or if the compressed version of IN was no
+ smaller than the original IN, OUT will be a copy of IN with the size
+ prepended as an integer. */
+static svn_error_t *
+zlib_encode(const char *data,
+ apr_size_t len,
+ svn_stringbuf_t *out,
+ int compression_level)
+{
+ unsigned long endlen;
+ apr_size_t intlen;
+ unsigned char buf[SVN__MAX_ENCODED_UINT_LEN], *p;
+
+ svn_stringbuf_setempty(out);
+ p = svn__encode_uint(buf, (apr_uint64_t)len);
+ svn_stringbuf_appendbytes(out, (const char *)buf, p - buf);
+
+ intlen = out->len;
+
+ /* Compression initialization overhead is considered to large for
+ short buffers. Also, if we don't actually want to compress data,
+ ZLIB will produce an output no shorter than the input. Hence,
+ the DATA would directly appended to OUT, so we can do that directly
+ without calling ZLIB before. */
+ if (len < MIN_COMPRESS_SIZE || compression_level == SVN__COMPRESSION_NONE)
+ {
+ svn_stringbuf_appendbytes(out, data, len);
+ }
+ else
+ {
+ int zerr;
+
+ svn_stringbuf_ensure(out, svnCompressBound(len) + intlen);
+ endlen = out->blocksize;
+
+ zerr = compress2((unsigned char *)out->data + intlen, &endlen,
+ (const unsigned char *)data, len,
+ compression_level);
+ if (zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(
+ zerr, "compress2",
+ _("Compression of svndiff data failed")));
+
+ /* Compression didn't help :(, just append the original text */
+ if (endlen >= len)
+ {
+ svn_stringbuf_appendbytes(out, data, len);
+ return SVN_NO_ERROR;
+ }
+ out->len = endlen + intlen;
+ out->data[out->len] = 0;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Decode the possibly-zlib compressed string of length INLEN that is in
+ IN, into OUT. We expect an integer is prepended to IN that specifies
+ the original size, and that if encoded size == original size, that the
+ remaining data is not compressed.
+ In that case, we will simply return pointer into IN as data pointer for
+ OUT, COPYLESS_ALLOWED has been set. The, the caller is expected not to
+ modify the contents of OUT.
+ An error is returned if the decoded length exceeds the given LIMIT.
+ */
+static svn_error_t *
+zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out,
+ apr_size_t limit)
+{
+ apr_size_t len;
+ apr_uint64_t size;
+ const unsigned char *oldplace = in;
+
+ /* First thing in the string is the original length. */
+ in = svn__decode_uint(&size, in, in + inLen);
+ len = (apr_size_t)size;
+ if (in == NULL || len != size)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL,
+ _("Decompression of zlib compressed data failed: no size"));
+ if (len > limit)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL,
+ _("Decompression of zlib compressed data failed: "
+ "size too large"));
+
+ /* We need to subtract the size of the encoded original length off the
+ * still remaining input length. */
+ inLen -= (in - oldplace);
+ if (inLen == len)
+ {
+ svn_stringbuf_ensure(out, len);
+ memcpy(out->data, in, len);
+ out->data[len] = 0;
+ out->len = len;
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ unsigned long zlen = len;
+ int zerr;
+
+ svn_stringbuf_ensure(out, len);
+ zerr = uncompress((unsigned char *)out->data, &zlen, in, inLen);
+ if (zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(
+ zerr, "uncompress",
+ _("Decompression of svndiff data failed")));
+
+ /* Zlib should not produce something that has a different size than the
+ original length we stored. */
+ if (zlen != len)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
+ NULL,
+ _("Size of uncompressed data "
+ "does not match stored original length"));
+ out->data[zlen] = 0;
+ out->len = zlen;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn__compress(svn_stringbuf_t *in,
+ svn_stringbuf_t *out,
+ int compression_method)
+{
+ if ( compression_method < SVN__COMPRESSION_NONE
+ || compression_method > SVN__COMPRESSION_ZLIB_MAX)
+ return svn_error_createf(SVN_ERR_BAD_COMPRESSION_METHOD, NULL,
+ _("Unsupported compression method %d"),
+ compression_method);
+
+ return zlib_encode(in->data, in->len, out, compression_method);
+}
+
+svn_error_t *
+svn__decompress(svn_stringbuf_t *in,
+ svn_stringbuf_t *out,
+ apr_size_t limit)
+{
+ return zlib_decode((const unsigned char*)in->data, in->len, out, limit);
+}