| /* |
| * svndiff.c -- Encoding and decoding svndiff-format deltas. |
| * |
| * ==================================================================== |
| * 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 <assert.h> |
| #include <string.h> |
| #include "svn_delta.h" |
| #include "svn_io.h" |
| #include "delta.h" |
| #include "svn_pools.h" |
| #include "svn_private_config.h" |
| |
| #include "private/svn_error_private.h" |
| #include "private/svn_delta_private.h" |
| #include "private/svn_subr_private.h" |
| #include "private/svn_string_private.h" |
| #include "private/svn_dep_compat.h" |
| |
| static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 }; |
| static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 }; |
| |
| #define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0)) |
| |
| static const char * |
| get_svndiff_header(int version) |
| { |
| if (version == 1) |
| return SVNDIFF_V1; |
| else |
| return SVNDIFF_V0; |
| } |
| |
| /* ----- Text delta to svndiff ----- */ |
| |
| /* We make one of these and get it passed back to us in calls to the |
| window handler. We only use it to record the write function and |
| baton passed to svn_txdelta_to_svndiff3(). */ |
| struct encoder_baton { |
| svn_stream_t *output; |
| svn_boolean_t header_done; |
| int version; |
| int compression_level; |
| /* Pool for temporary allocations, will be cleared periodically. */ |
| apr_pool_t *scratch_pool; |
| }; |
| |
| /* This is at least as big as the largest size for a single instruction. */ |
| #define MAX_INSTRUCTION_LEN (2*SVN__MAX_ENCODED_UINT_LEN+1) |
| /* This is at least as big as the largest possible instructions |
| section: in theory, the instructions could be SVN_DELTA_WINDOW_SIZE |
| 1-byte copy-from-source instructions (though this is very unlikely). */ |
| #define MAX_INSTRUCTION_SECTION_LEN (SVN_DELTA_WINDOW_SIZE*MAX_INSTRUCTION_LEN) |
| |
| |
| /* Append an encoded integer to a string. */ |
| static void |
| append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val) |
| { |
| unsigned char buf[SVN__MAX_ENCODED_UINT_LEN], *p; |
| |
| SVN_ERR_ASSERT_NO_RETURN(val >= 0); |
| p = svn__encode_uint(buf, (apr_uint64_t)val); |
| svn_stringbuf_appendbytes(header, (const char *)buf, p - buf); |
| } |
| |
| static svn_error_t * |
| send_simple_insertion_window(svn_txdelta_window_t *window, |
| struct encoder_baton *eb) |
| { |
| unsigned char headers[SVNDIFF_HEADER_SIZE + 5 * SVN__MAX_ENCODED_UINT_LEN |
| + MAX_INSTRUCTION_LEN]; |
| unsigned char ibuf[MAX_INSTRUCTION_LEN]; |
| unsigned char *header_current; |
| apr_size_t header_len; |
| apr_size_t ip_len, i; |
| apr_size_t len = window->new_data->len; |
| |
| /* there is only one target copy op. It must span the whole window */ |
| assert(window->ops[0].action_code == svn_txdelta_new); |
| assert(window->ops[0].length == window->tview_len); |
| assert(window->ops[0].offset == 0); |
| |
| /* write stream header if necessary */ |
| if (!eb->header_done) |
| { |
| eb->header_done = TRUE; |
| memcpy(headers, get_svndiff_header(eb->version), SVNDIFF_HEADER_SIZE); |
| header_current = headers + SVNDIFF_HEADER_SIZE; |
| } |
| else |
| { |
| header_current = headers; |
| } |
| |
| /* Encode the action code and length. */ |
| if (window->tview_len >> 6 == 0) |
| { |
| ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6)); |
| ip_len = 1; |
| } |
| else |
| { |
| ibuf[0] = (0x2 << 6); |
| ip_len = svn__encode_uint(ibuf + 1, window->tview_len) - ibuf; |
| } |
| |
| /* encode the window header. Please note that the source window may |
| * have content despite not being used for deltification. */ |
| header_current = svn__encode_uint(header_current, |
| (apr_uint64_t)window->sview_offset); |
| header_current = svn__encode_uint(header_current, window->sview_len); |
| header_current = svn__encode_uint(header_current, window->tview_len); |
| header_current[0] = (unsigned char)ip_len; /* 1 instruction */ |
| header_current = svn__encode_uint(&header_current[1], len); |
| |
| /* append instructions (1 to a handful of bytes) */ |
| for (i = 0; i < ip_len; ++i) |
| header_current[i] = ibuf[i]; |
| |
| header_len = header_current - headers + ip_len; |
| |
| /* Write out the window. */ |
| SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len)); |
| if (len) |
| SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Encodes delta window WINDOW to svndiff-format. |
| The svndiff version is VERSION. COMPRESSION_LEVEL is the zlib |
| compression level to use. |
| Returned values will be allocated in POOL or refer to *WINDOW |
| fields. */ |
| static svn_error_t * |
| encode_window(svn_stringbuf_t **instructions_p, |
| svn_stringbuf_t **header_p, |
| const svn_string_t **newdata_p, |
| svn_txdelta_window_t *window, |
| int version, |
| int compression_level, |
| apr_pool_t *pool) |
| { |
| svn_stringbuf_t *instructions; |
| svn_stringbuf_t *header; |
| const svn_string_t *newdata; |
| unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip; |
| const svn_txdelta_op_t *op; |
| |
| /* create the necessary data buffers */ |
| instructions = svn_stringbuf_create_empty(pool); |
| header = svn_stringbuf_create_empty(pool); |
| |
| /* Encode the instructions. */ |
| for (op = window->ops; op < window->ops + window->num_ops; op++) |
| { |
| /* Encode the action code and length. */ |
| ip = ibuf; |
| switch (op->action_code) |
| { |
| case svn_txdelta_source: *ip = 0; break; |
| case svn_txdelta_target: *ip = (0x1 << 6); break; |
| case svn_txdelta_new: *ip = (0x2 << 6); break; |
| } |
| if (op->length >> 6 == 0) |
| *ip++ |= (unsigned char)op->length; |
| else |
| ip = svn__encode_uint(ip + 1, op->length); |
| if (op->action_code != svn_txdelta_new) |
| ip = svn__encode_uint(ip, op->offset); |
| svn_stringbuf_appendbytes(instructions, (const char *)ibuf, ip - ibuf); |
| } |
| |
| /* Encode the header. */ |
| append_encoded_int(header, window->sview_offset); |
| append_encoded_int(header, window->sview_len); |
| append_encoded_int(header, window->tview_len); |
| if (version == 1) |
| { |
| svn_stringbuf_t *compressed_instructions; |
| compressed_instructions = svn_stringbuf_create_empty(pool); |
| SVN_ERR(svn__compress(instructions->data, instructions->len, |
| compressed_instructions, compression_level)); |
| instructions = compressed_instructions; |
| } |
| append_encoded_int(header, instructions->len); |
| if (version == 1) |
| { |
| svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool); |
| |
| SVN_ERR(svn__compress(window->new_data->data, window->new_data->len, |
| compressed, compression_level)); |
| newdata = svn_stringbuf__morph_into_string(compressed); |
| } |
| else |
| newdata = window->new_data; |
| |
| append_encoded_int(header, newdata->len); |
| |
| *instructions_p = instructions; |
| *header_p = header; |
| *newdata_p = newdata; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| window_handler(svn_txdelta_window_t *window, void *baton) |
| { |
| struct encoder_baton *eb = baton; |
| apr_size_t len; |
| svn_stringbuf_t *instructions; |
| svn_stringbuf_t *header; |
| const svn_string_t *newdata; |
| |
| /* use specialized code if there is no source */ |
| if (window && !window->src_ops && window->num_ops == 1 && !eb->version) |
| return svn_error_trace(send_simple_insertion_window(window, eb)); |
| |
| /* Make sure we write the header. */ |
| if (!eb->header_done) |
| { |
| len = SVNDIFF_HEADER_SIZE; |
| SVN_ERR(svn_stream_write(eb->output, get_svndiff_header(eb->version), |
| &len)); |
| eb->header_done = TRUE; |
| } |
| |
| if (window == NULL) |
| { |
| /* We're done; clean up. */ |
| SVN_ERR(svn_stream_close(eb->output)); |
| |
| svn_pool_destroy(eb->scratch_pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_pool_clear(eb->scratch_pool); |
| |
| SVN_ERR(encode_window(&instructions, &header, &newdata, window, |
| eb->version, eb->compression_level, |
| eb->scratch_pool)); |
| |
| /* Write out the window. */ |
| len = header->len; |
| SVN_ERR(svn_stream_write(eb->output, header->data, &len)); |
| if (instructions->len > 0) |
| { |
| len = instructions->len; |
| SVN_ERR(svn_stream_write(eb->output, instructions->data, &len)); |
| } |
| if (newdata->len > 0) |
| { |
| len = newdata->len; |
| SVN_ERR(svn_stream_write(eb->output, newdata->data, &len)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| void |
| svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler, |
| void **handler_baton, |
| svn_stream_t *output, |
| int svndiff_version, |
| int compression_level, |
| apr_pool_t *pool) |
| { |
| struct encoder_baton *eb; |
| |
| eb = apr_palloc(pool, sizeof(*eb)); |
| eb->output = output; |
| eb->header_done = FALSE; |
| eb->scratch_pool = svn_pool_create(pool); |
| eb->version = svndiff_version; |
| eb->compression_level = compression_level; |
| |
| *handler = window_handler; |
| *handler_baton = eb; |
| } |
| |
| void |
| svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler, |
| void **handler_baton, |
| svn_stream_t *output, |
| int svndiff_version, |
| apr_pool_t *pool) |
| { |
| svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version, |
| SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); |
| } |
| |
| void |
| svn_txdelta_to_svndiff(svn_stream_t *output, |
| apr_pool_t *pool, |
| svn_txdelta_window_handler_t *handler, |
| void **handler_baton) |
| { |
| svn_txdelta_to_svndiff3(handler, handler_baton, output, 0, |
| SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); |
| } |
| |
| |
| /* ----- svndiff to text delta ----- */ |
| |
| /* An svndiff parser object. */ |
| struct decode_baton |
| { |
| /* Once the svndiff parser has enough data buffered to create a |
| "window", it passes this window to the caller's consumer routine. */ |
| svn_txdelta_window_handler_t consumer_func; |
| void *consumer_baton; |
| |
| /* Pool to create subpools from; each developing window will be a |
| subpool. */ |
| apr_pool_t *pool; |
| |
| /* The current subpool which contains our current window-buffer. */ |
| apr_pool_t *subpool; |
| |
| /* The actual svndiff data buffer, living within subpool. */ |
| svn_stringbuf_t *buffer; |
| |
| /* The offset and size of the last source view, so that we can check |
| to make sure the next one isn't sliding backwards. */ |
| svn_filesize_t last_sview_offset; |
| apr_size_t last_sview_len; |
| |
| /* We have to discard four bytes at the beginning for the header. |
| This field keeps track of how many of those bytes we have read. */ |
| apr_size_t header_bytes; |
| |
| /* Do we want an error to occur when we close the stream that |
| indicates we didn't send the whole svndiff data? If you plan to |
| not transmit the whole svndiff data stream, you will want this to |
| be FALSE. */ |
| svn_boolean_t error_on_early_close; |
| |
| /* svndiff version in use by delta. */ |
| unsigned char version; |
| |
| /* Length of parsed delta window header. 0 if window is not parsed yet. */ |
| apr_size_t window_header_len; |
| |
| /* Five integer fields of parsed delta window header. Valid only if |
| WINDOW_HEADER_LEN > 0 */ |
| svn_filesize_t sview_offset; |
| apr_size_t sview_len; |
| apr_size_t tview_len; |
| apr_size_t inslen; |
| apr_size_t newlen; |
| }; |
| |
| |
| /* Wrapper aroung svn__deencode_uint taking a file size as *VAL. */ |
| static const unsigned char * |
| decode_file_offset(svn_filesize_t *val, |
| const unsigned char *p, |
| const unsigned char *end) |
| { |
| apr_uint64_t temp = 0; |
| const unsigned char *result = svn__decode_uint(&temp, p, end); |
| *val = (svn_filesize_t)temp; |
| |
| return result; |
| } |
| |
| /* Same as above, only decode into a size variable. */ |
| static const unsigned char * |
| decode_size(apr_size_t *val, |
| const unsigned char *p, |
| const unsigned char *end) |
| { |
| apr_uint64_t temp = 0; |
| const unsigned char *result = svn__decode_uint(&temp, p, end); |
| if (temp > APR_SIZE_MAX) |
| return NULL; |
| |
| *val = (apr_size_t)temp; |
| return result; |
| } |
| |
| /* Decode an instruction into OP, returning a pointer to the text |
| after the instruction. Note that if the action code is |
| svn_txdelta_new, the offset field of *OP will not be set. */ |
| static const unsigned char * |
| decode_instruction(svn_txdelta_op_t *op, |
| const unsigned char *p, |
| const unsigned char *end) |
| { |
| apr_size_t c; |
| apr_size_t action; |
| |
| if (p == end) |
| return NULL; |
| |
| /* We need this more than once */ |
| c = *p++; |
| |
| /* Decode the instruction selector. */ |
| action = (c >> 6) & 0x3; |
| if (action >= 0x3) |
| return NULL; |
| |
| /* This relies on enum svn_delta_action values to match and never to be |
| redefined. */ |
| op->action_code = (enum svn_delta_action)(action); |
| |
| /* Decode the length and offset. */ |
| op->length = c & 0x3f; |
| if (op->length == 0) |
| { |
| p = decode_size(&op->length, p, end); |
| if (p == NULL) |
| return NULL; |
| } |
| if (action != svn_txdelta_new) |
| { |
| p = decode_size(&op->offset, p, end); |
| if (p == NULL) |
| return NULL; |
| } |
| |
| return p; |
| } |
| |
| /* Count the instructions in the range [P..END-1] and make sure they |
| are valid for the given window lengths. Return an error if the |
| instructions are invalid; otherwise set *NINST to the number of |
| instructions. */ |
| static svn_error_t * |
| count_and_verify_instructions(int *ninst, |
| const unsigned char *p, |
| const unsigned char *end, |
| apr_size_t sview_len, |
| apr_size_t tview_len, |
| apr_size_t new_len) |
| { |
| int n = 0; |
| svn_txdelta_op_t op; |
| apr_size_t tpos = 0, npos = 0; |
| |
| while (p < end) |
| { |
| p = decode_instruction(&op, p, end); |
| |
| /* Detect any malformed operations from the instruction stream. */ |
| if (p == NULL) |
| return svn_error_createf |
| (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Invalid diff stream: insn %d cannot be decoded"), n); |
| else if (op.length == 0) |
| return svn_error_createf |
| (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Invalid diff stream: insn %d has length zero"), n); |
| else if (op.length > tview_len - tpos) |
| return svn_error_createf |
| (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Invalid diff stream: insn %d overflows the target view"), n); |
| |
| switch (op.action_code) |
| { |
| case svn_txdelta_source: |
| if (op.length > sview_len - op.offset || |
| op.offset > sview_len) |
| return svn_error_createf |
| (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Invalid diff stream: " |
| "[src] insn %d overflows the source view"), n); |
| break; |
| case svn_txdelta_target: |
| if (op.offset >= tpos) |
| return svn_error_createf |
| (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Invalid diff stream: " |
| "[tgt] insn %d starts beyond the target view position"), n); |
| break; |
| case svn_txdelta_new: |
| if (op.length > new_len - npos) |
| return svn_error_createf |
| (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Invalid diff stream: " |
| "[new] insn %d overflows the new data section"), n); |
| npos += op.length; |
| break; |
| } |
| tpos += op.length; |
| n++; |
| } |
| if (tpos != tview_len) |
| return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Delta does not fill the target window")); |
| if (npos != new_len) |
| return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL, |
| _("Delta does not contain enough new data")); |
| |
| *ninst = n; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Given the five integer fields of a window header and a pointer to |
| the remainder of the window contents, fill in a delta window |
| structure *WINDOW. New allocations will be performed in POOL; |
| the new_data field of *WINDOW will refer directly to memory pointed |
| to by DATA. */ |
| static svn_error_t * |
| decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset, |
| apr_size_t sview_len, apr_size_t tview_len, apr_size_t inslen, |
| apr_size_t newlen, const unsigned char *data, apr_pool_t *pool, |
| unsigned int version) |
| { |
| const unsigned char *insend; |
| int ninst; |
| apr_size_t npos; |
| svn_txdelta_op_t *ops, *op; |
| svn_string_t *new_data; |
| |
| window->sview_offset = sview_offset; |
| window->sview_len = sview_len; |
| window->tview_len = tview_len; |
| |
| insend = data + inslen; |
| |
| if (version == 1) |
| { |
| svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool); |
| svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool); |
| |
| SVN_ERR(svn__decompress(insend, newlen, ndout, |
| SVN_DELTA_WINDOW_SIZE)); |
| SVN_ERR(svn__decompress(data, insend - data, instout, |
| MAX_INSTRUCTION_SECTION_LEN)); |
| |
| newlen = ndout->len; |
| data = (unsigned char *)instout->data; |
| insend = (unsigned char *)instout->data + instout->len; |
| |
| new_data = svn_stringbuf__morph_into_string(ndout); |
| } |
| else |
| { |
| /* Copy the data because an svn_string_t must have the invariant |
| data[len]=='\0'. */ |
| new_data = svn_string_ncreate((const char*)insend, newlen, pool); |
| } |
| |
| /* Count the instructions and make sure they are all valid. */ |
| SVN_ERR(count_and_verify_instructions(&ninst, data, insend, |
| sview_len, tview_len, newlen)); |
| |
| /* Allocate a buffer for the instructions and decode them. */ |
| ops = apr_palloc(pool, ninst * sizeof(*ops)); |
| npos = 0; |
| window->src_ops = 0; |
| for (op = ops; op < ops + ninst; op++) |
| { |
| data = decode_instruction(op, data, insend); |
| if (op->action_code == svn_txdelta_source) |
| ++window->src_ops; |
| else if (op->action_code == svn_txdelta_new) |
| { |
| op->offset = npos; |
| npos += op->length; |
| } |
| } |
| SVN_ERR_ASSERT(data == insend); |
| |
| window->ops = ops; |
| window->num_ops = ninst; |
| window->new_data = new_data; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| write_handler(void *baton, |
| const char *buffer, |
| apr_size_t *len) |
| { |
| struct decode_baton *db = (struct decode_baton *) baton; |
| const unsigned char *p, *end; |
| apr_size_t buflen = *len; |
| |
| /* Chew up four bytes at the beginning for the header. */ |
| if (db->header_bytes < SVNDIFF_HEADER_SIZE) |
| { |
| apr_size_t nheader = SVNDIFF_HEADER_SIZE - db->header_bytes; |
| if (nheader > buflen) |
| nheader = buflen; |
| if (memcmp(buffer, SVNDIFF_V0 + db->header_bytes, nheader) == 0) |
| db->version = 0; |
| else if (memcmp(buffer, SVNDIFF_V1 + db->header_bytes, nheader) == 0) |
| db->version = 1; |
| else |
| return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL, |
| _("Svndiff has invalid header")); |
| buflen -= nheader; |
| buffer += nheader; |
| db->header_bytes += nheader; |
| } |
| |
| /* Concatenate the old with the new. */ |
| svn_stringbuf_appendbytes(db->buffer, buffer, buflen); |
| |
| /* We have a buffer of svndiff data that might be good for: |
| |
| a) an integral number of windows' worth of data - this is a |
| trivial case. Make windows from our data and ship them off. |
| |
| b) a non-integral number of windows' worth of data - we shall |
| consume the integral portion of the window data, and then |
| somewhere in the following loop the decoding of the svndiff |
| data will run out of stuff to decode, and will simply return |
| SVN_NO_ERROR, anxiously awaiting more data. |
| */ |
| |
| while (1) |
| { |
| svn_txdelta_window_t window; |
| |
| /* Read the header, if we have enough bytes for that. */ |
| p = (const unsigned char *) db->buffer->data; |
| end = (const unsigned char *) db->buffer->data + db->buffer->len; |
| |
| if (db->window_header_len == 0) |
| { |
| svn_filesize_t sview_offset; |
| apr_size_t sview_len, tview_len, inslen, newlen; |
| const unsigned char *hdr_start = p; |
| |
| p = decode_file_offset(&sview_offset, p, end); |
| if (p == NULL) |
| break; |
| |
| p = decode_size(&sview_len, p, end); |
| if (p == NULL) |
| break; |
| |
| p = decode_size(&tview_len, p, end); |
| if (p == NULL) |
| break; |
| |
| p = decode_size(&inslen, p, end); |
| if (p == NULL) |
| break; |
| |
| p = decode_size(&newlen, p, end); |
| if (p == NULL) |
| break; |
| |
| if (tview_len > SVN_DELTA_WINDOW_SIZE || |
| sview_len > SVN_DELTA_WINDOW_SIZE || |
| /* for svndiff1, newlen includes the original length */ |
| newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN || |
| inslen > MAX_INSTRUCTION_SECTION_LEN) |
| return svn_error_create( |
| SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, |
| _("Svndiff contains a too-large window")); |
| |
| /* Check for integer overflow. */ |
| if (sview_offset < 0 || inslen + newlen < inslen |
| || sview_len + tview_len < sview_len |
| || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset) |
| return svn_error_create( |
| SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, |
| _("Svndiff contains corrupt window header")); |
| |
| /* Check for source windows which slide backwards. */ |
| if (sview_len > 0 |
| && (sview_offset < db->last_sview_offset |
| || (sview_offset + sview_len |
| < db->last_sview_offset + db->last_sview_len))) |
| return svn_error_create( |
| SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL, |
| _("Svndiff has backwards-sliding source views")); |
| |
| /* Remember parsed window header. */ |
| db->window_header_len = p - hdr_start; |
| db->sview_offset = sview_offset; |
| db->sview_len = sview_len; |
| db->tview_len = tview_len; |
| db->inslen = inslen; |
| db->newlen = newlen; |
| } |
| else |
| { |
| /* Skip already parsed window header. */ |
| p += db->window_header_len; |
| } |
| |
| /* Wait for more data if we don't have enough bytes for the |
| whole window. */ |
| if ((apr_size_t) (end - p) < db->inslen + db->newlen) |
| return SVN_NO_ERROR; |
| |
| /* Decode the window and send it off. */ |
| SVN_ERR(decode_window(&window, db->sview_offset, db->sview_len, |
| db->tview_len, db->inslen, db->newlen, p, |
| db->subpool, db->version)); |
| SVN_ERR(db->consumer_func(&window, db->consumer_baton)); |
| |
| p += db->inslen + db->newlen; |
| |
| /* Remove processed data from the buffer. */ |
| svn_stringbuf_remove(db->buffer, 0, db->buffer->len - (end - p)); |
| |
| /* Reset window header length. */ |
| db->window_header_len = 0; |
| |
| /* Remember the offset and length of the source view for next time. */ |
| db->last_sview_offset = db->sview_offset; |
| db->last_sview_len = db->sview_len; |
| |
| /* Clear subpool. */ |
| svn_pool_clear(db->subpool); |
| } |
| |
| /* At this point we processed all integral windows and DB->BUFFER is empty |
| or contains partially read window header. |
| Check that unprocessed data is not larger than theoretical maximum |
| window header size. */ |
| if (db->buffer->len > 5 * SVN__MAX_ENCODED_UINT_LEN) |
| return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, |
| _("Svndiff contains a too-large window header")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Minimal svn_stream_t write handler, doing nothing */ |
| static svn_error_t * |
| noop_write_handler(void *baton, |
| const char *buffer, |
| apr_size_t *len) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| close_handler(void *baton) |
| { |
| struct decode_baton *db = (struct decode_baton *) baton; |
| svn_error_t *err; |
| |
| /* Make sure that we're at a plausible end of stream, returning an |
| error if we are expected to do so. */ |
| if ((db->error_on_early_close) |
| && (db->header_bytes < 4 || db->buffer->len != 0)) |
| return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, |
| _("Unexpected end of svndiff input")); |
| |
| /* Tell the window consumer that we're done, and clean up. */ |
| err = db->consumer_func(NULL, db->consumer_baton); |
| svn_pool_destroy(db->pool); |
| return err; |
| } |
| |
| |
| svn_stream_t * |
| svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler, |
| void *handler_baton, |
| svn_boolean_t error_on_early_close, |
| apr_pool_t *pool) |
| { |
| svn_stream_t *stream; |
| |
| if (handler != svn_delta_noop_window_handler) |
| { |
| apr_pool_t *subpool = svn_pool_create(pool); |
| struct decode_baton *db = apr_palloc(pool, sizeof(*db)); |
| |
| db->consumer_func = handler; |
| db->consumer_baton = handler_baton; |
| db->pool = subpool; |
| db->subpool = svn_pool_create(subpool); |
| db->buffer = svn_stringbuf_create_empty(db->pool); |
| db->last_sview_offset = 0; |
| db->last_sview_len = 0; |
| db->header_bytes = 0; |
| db->error_on_early_close = error_on_early_close; |
| db->window_header_len = 0; |
| stream = svn_stream_create(db, pool); |
| |
| svn_stream_set_write(stream, write_handler); |
| svn_stream_set_close(stream, close_handler); |
| } |
| else |
| { |
| /* And else we just ignore everything as efficiently as we can. |
| by only hooking a no-op handler */ |
| stream = svn_stream_create(NULL, pool); |
| svn_stream_set_write(stream, noop_write_handler); |
| } |
| return stream; |
| } |
| |
| |
| /* Routines for reading one svndiff window at a time. */ |
| |
| /* Read one byte from STREAM into *BYTE. */ |
| static svn_error_t * |
| read_one_byte(unsigned char *byte, svn_stream_t *stream) |
| { |
| char c; |
| apr_size_t len = 1; |
| |
| SVN_ERR(svn_stream_read_full(stream, &c, &len)); |
| if (len == 0) |
| return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, |
| _("Unexpected end of svndiff input")); |
| *byte = (unsigned char) c; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Read and decode one integer from STREAM into *SIZE. |
| Increment *BYTE_COUNTER by the number of chars we have read. */ |
| static svn_error_t * |
| read_one_size(apr_size_t *size, |
| apr_size_t *byte_counter, |
| svn_stream_t *stream) |
| { |
| unsigned char c; |
| |
| *size = 0; |
| while (1) |
| { |
| SVN_ERR(read_one_byte(&c, stream)); |
| ++*byte_counter; |
| *size = (*size << 7) | (c & 0x7f); |
| if (!(c & 0x80)) |
| break; |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| /* Read a window header from STREAM and check it for integer overflow. */ |
| static svn_error_t * |
| read_window_header(svn_stream_t *stream, svn_filesize_t *sview_offset, |
| apr_size_t *sview_len, apr_size_t *tview_len, |
| apr_size_t *inslen, apr_size_t *newlen, |
| apr_size_t *header_len) |
| { |
| unsigned char c; |
| |
| /* Read the source view offset by hand, since it's not an apr_size_t. */ |
| *header_len = 0; |
| *sview_offset = 0; |
| while (1) |
| { |
| SVN_ERR(read_one_byte(&c, stream)); |
| ++*header_len; |
| *sview_offset = (*sview_offset << 7) | (c & 0x7f); |
| if (!(c & 0x80)) |
| break; |
| } |
| |
| /* Read the four size fields. */ |
| SVN_ERR(read_one_size(sview_len, header_len, stream)); |
| SVN_ERR(read_one_size(tview_len, header_len, stream)); |
| SVN_ERR(read_one_size(inslen, header_len, stream)); |
| SVN_ERR(read_one_size(newlen, header_len, stream)); |
| |
| if (*tview_len > SVN_DELTA_WINDOW_SIZE || |
| *sview_len > SVN_DELTA_WINDOW_SIZE || |
| /* for svndiff1, newlen includes the original length */ |
| *newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN || |
| *inslen > MAX_INSTRUCTION_SECTION_LEN) |
| return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, |
| _("Svndiff contains a too-large window")); |
| |
| /* Check for integer overflow. */ |
| if (*sview_offset < 0 || *inslen + *newlen < *inslen |
| || *sview_len + *tview_len < *sview_len |
| || (apr_size_t)*sview_offset + *sview_len < (apr_size_t)*sview_offset) |
| return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, |
| _("Svndiff contains corrupt window header")); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window, |
| svn_stream_t *stream, |
| int svndiff_version, |
| apr_pool_t *pool) |
| { |
| svn_filesize_t sview_offset; |
| apr_size_t sview_len, tview_len, inslen, newlen, len, header_len; |
| unsigned char *buf; |
| |
| SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, |
| &inslen, &newlen, &header_len)); |
| len = inslen + newlen; |
| buf = apr_palloc(pool, len); |
| SVN_ERR(svn_stream_read_full(stream, (char*)buf, &len)); |
| if (len < inslen + newlen) |
| return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, |
| _("Unexpected end of svndiff input")); |
| *window = apr_palloc(pool, sizeof(**window)); |
| return decode_window(*window, sview_offset, sview_len, tview_len, inslen, |
| newlen, buf, pool, svndiff_version); |
| } |
| |
| |
| svn_error_t * |
| svn_txdelta_skip_svndiff_window(apr_file_t *file, |
| int svndiff_version, |
| apr_pool_t *pool) |
| { |
| svn_stream_t *stream = svn_stream_from_aprfile2(file, TRUE, pool); |
| svn_filesize_t sview_offset; |
| apr_size_t sview_len, tview_len, inslen, newlen, header_len; |
| apr_off_t offset; |
| |
| SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, |
| &inslen, &newlen, &header_len)); |
| |
| offset = inslen + newlen; |
| return svn_io_file_seek(file, APR_CUR, &offset, pool); |
| } |
| |
| svn_error_t * |
| svn_txdelta__read_raw_window_len(apr_size_t *window_len, |
| svn_stream_t *stream, |
| apr_pool_t *pool) |
| { |
| svn_filesize_t sview_offset; |
| apr_size_t sview_len, tview_len, inslen, newlen, header_len; |
| |
| SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, |
| &inslen, &newlen, &header_len)); |
| |
| *window_len = inslen + newlen + header_len; |
| return SVN_NO_ERROR; |
| } |
| |