/* * binary_diff.c: handling of git like binary diffs * * ==================================================================== * 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 #include "svn_pools.h" #include "svn_error.h" #include "svn_diff.h" #include "svn_types.h" #include "diff.h" #include "svn_private_config.h" /* Copies the data from ORIGINAL_STREAM to a temporary file, returning both the original and compressed size. */ static svn_error_t * create_compressed(apr_file_t **result, svn_filesize_t *full_size, svn_filesize_t *compressed_size, svn_stream_t *original_stream, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_stream_t *compressed; svn_filesize_t bytes_read = 0; apr_size_t rd; SVN_ERR(svn_io_open_uniquely_named(result, NULL, NULL, "diffgz", NULL, svn_io_file_del_on_pool_cleanup, result_pool, scratch_pool)); compressed = svn_stream_compressed( svn_stream_from_aprfile2(*result, TRUE, scratch_pool), scratch_pool); if (original_stream) do { char buffer[SVN__STREAM_CHUNK_SIZE]; rd = sizeof(buffer); if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); SVN_ERR(svn_stream_read_full(original_stream, buffer, &rd)); bytes_read += rd; SVN_ERR(svn_stream_write(compressed, buffer, &rd)); } while(rd == SVN__STREAM_CHUNK_SIZE); else { apr_size_t zero = 0; SVN_ERR(svn_stream_write(compressed, NULL, &zero)); } SVN_ERR(svn_stream_close(compressed)); /* Flush compression */ *full_size = bytes_read; SVN_ERR(svn_io_file_size_get(compressed_size, *result, scratch_pool)); return SVN_NO_ERROR; } #define GIT_BASE85_CHUNKSIZE 52 /* Git Base-85 table for write_literal */ static const char b85str[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "!#$%&()*+-;<=>?@^_`{|}~"; /* Helper function for svn_diff__base85_decode_line */ static svn_error_t * base85_value(int *value, char c) { const char *p = strchr(b85str, c); if (!p) return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL, _("Invalid base85 value")); /* It's safe to cast the ptrdiff_t value of the pointer difference to int because the value will always be in the range [0..84]. */ *value = (int)(p - b85str); return SVN_NO_ERROR; } svn_error_t * svn_diff__base85_decode_line(char *output_data, apr_ssize_t output_len, const char *base85_data, apr_ssize_t base85_len, apr_pool_t *scratch_pool) { { apr_ssize_t expected_data = (output_len + 3) / 4 * 5; if (base85_len != expected_data) return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL, _("Unexpected base85 line length")); } while (base85_len) { unsigned info = 0; apr_ssize_t i, n; for (i = 0; i < 5; i++) { int value; SVN_ERR(base85_value(&value, base85_data[i])); info *= 85; info += value; } for (i = 0, n=24; i < 4; i++, n-=8) { if (i < output_len) output_data[i] = (info >> n) & 0xFF; } base85_data += 5; base85_len -= 5; output_data += 4; output_len -= 4; } return SVN_NO_ERROR; } /* Git length encoding table for write_literal */ static const char b85lenstr[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; /* Writes out a git-like literal output of the compressed data in COMPRESSED_DATA to OUTPUT_STREAM, describing that its normal length is UNCOMPRESSED_SIZE. */ static svn_error_t * write_literal(svn_filesize_t uncompressed_size, svn_stream_t *compressed_data, svn_stream_t *output_stream, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { apr_size_t rd; SVN_ERR(svn_stream_seek(compressed_data, NULL)); /* Seek to start */ SVN_ERR(svn_stream_printf(output_stream, scratch_pool, "literal %" SVN_FILESIZE_T_FMT APR_EOL_STR, uncompressed_size)); do { char chunk[GIT_BASE85_CHUNKSIZE]; const unsigned char *next; apr_size_t left; rd = sizeof(chunk); if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); SVN_ERR(svn_stream_read_full(compressed_data, chunk, &rd)); { apr_size_t one = 1; SVN_ERR(svn_stream_write(output_stream, &b85lenstr[rd-1], &one)); } left = rd; next = (void*)chunk; while (left) { char five[5]; unsigned info = 0; int n; apr_size_t five_sz; /* Push 4 bytes into the 32 bit info, when available */ for (n = 24; n >= 0 && left; n -= 8, next++, left--) { info |= (*next) << n; } /* Write out info as base85 */ for (n = 4; n >= 0; n--) { five[n] = b85str[info % 85]; info /= 85; } five_sz = 5; SVN_ERR(svn_stream_write(output_stream, five, &five_sz)); } SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR)); } while (rd == GIT_BASE85_CHUNKSIZE); return SVN_NO_ERROR; } svn_error_t * svn_diff_output_binary(svn_stream_t *output_stream, svn_stream_t *original, svn_stream_t *latest, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { apr_file_t *original_apr; svn_filesize_t original_full; svn_filesize_t original_deflated; apr_file_t *latest_apr; svn_filesize_t latest_full; svn_filesize_t latest_deflated; apr_pool_t *subpool = svn_pool_create(scratch_pool); SVN_ERR(create_compressed(&original_apr, &original_full, &original_deflated, original, cancel_func, cancel_baton, scratch_pool, subpool)); svn_pool_clear(subpool); SVN_ERR(create_compressed(&latest_apr, &latest_full, &latest_deflated, latest, cancel_func, cancel_baton, scratch_pool, subpool)); svn_pool_clear(subpool); SVN_ERR(svn_stream_puts(output_stream, "GIT binary patch" APR_EOL_STR)); /* ### git would first calculate if a git-delta latest->original would be shorter than the zipped data. For now lets assume that it is not and just dump the literal data */ SVN_ERR(write_literal(latest_full, svn_stream_from_aprfile2(latest_apr, FALSE, subpool), output_stream, cancel_func, cancel_baton, scratch_pool)); svn_pool_clear(subpool); SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR)); /* ### git would first calculate if a git-delta original->latest would be shorter than the zipped data. For now lets assume that it is not and just dump the literal data */ SVN_ERR(write_literal(original_full, svn_stream_from_aprfile2(original_apr, FALSE, subpool), output_stream, cancel_func, cancel_baton, scratch_pool)); svn_pool_destroy(subpool); return SVN_NO_ERROR; }